Foundations.Control.Future

A Future is a class that provides a mechanism for implementing asychronous callbacks. The advantage with using Futures is that it handles results and exceptions in a more flexible way than traditional callback mechanisms.

Using Foundations.Control.Future

Allocating a Future

The following is a simple example of allocating a Future:

// Load Foundation libraries
var libraries = MojoLoader.require({ name: "foundations", version: "1.0" });

// Create reference to Futures class
var Future = libraries["foundations"].Control.Future;

// Allocate Future
var f = new Future("Hello");  // Allocate Future

Every Future has a "result" property and the above allocation initializes its value to "Hello".

Future Model

The Future model is a series of stages: "Do this, then, when done, do this with the result". Each stage is created with an invocation of the Future object's "then" method. The "then" method registers a function that is invoked when the "result" property gets set.

Here is a simple example:

var libraries = MojoLoader.require({ name: "foundations", version: "1.0" }); 
var Future = libraries["foundations"].Control.Future;
var f = new Future("Hello");

f.then(function(future) {
  if (future.result == "Hello") {
      // Do some processing...
      future.result = "Success";
  }
});

f.then(function(future) {
  if (future.result == "Success") {
      // Do some more processing...
      future.result = "Did stage 2";
  }
});

f.then(function(future) {
  if (future.exception) {
      future.result = false;
      // Log exception...
  }
  else {
      // Total operation success
      future.result = true;
  }
});

Here is a more involved example -- a Foundations AJAX call is made to retrieve a user's remote ID, which is then used to search the local db8 database for that user's contact information:

var libraries = MojoLoader.require({ name: "foundations", version: "1.0" });
var Future = libraries["foundations"].Control.Future;
var DB = libraries["foundations"].Data.DB;
var AjaxCall = libraries["foundations"].Comms.AjaxCall;

var f = AjaxCall.get("http://www.foobar.com/tdeAnswer");  // Returns a Future

f.then(this, function(future)
{
  var status = future.result.status;

  if (status === 200 ) // 200 = Success
  {
      var response = future.result.responseJSON;
      var id = response.id;
      var query = {
          from: "com.palm.contacts:1",
          where: [{ "prop": "remote_id", "op": "=", "val": id }]
      };
      return DB.find(query);  // Call returns a Future
  }
  future.result = { returnValue : false, status: status };
});

f.then(function(future) {
  if (future.result.returnValue === true)
  {
      var record = future.result.results[0];  // Get first record from query
      Mojo.Log.info("record =" + JSON.stringify(record));
      future.result = { returnValue: true };
  }
  else {
      future.result = { returnValue: false };
  }   
});

The following is happening here:

  • The AjaxCall creates a Future and returns it to the caller.

  • The returned Future calls its "then" method, which registers a scope ("this") and a function that is executed when the Future's "result" property gets set (from the AjaxCall).

  • The "then" function is called when the "result" gets set. The triggered Future is passed in as the only argument. Note that if "result" had been set before the "then" method was called, the registered function would have been immediately called.

  • In the registered function, the Future's "result" property is read. If the Future contains an "exception", rather than returning this, the "exception" is re-thrown here. See the next section on error handling for details.

  • The retrieved "id" value is used in a db8 find query to look up the user's record in the local db8 database. Since "DB.find" also returns a Future, we can return that result from the "then" to chain the two Futures together.

  • Finally, when the record has been retrieved from db8 storage, it is logged to the console.

Basic Usage

The following is the basic pattern for using Futures:

  • Create a Future or obtain one from a Foundations library call (i.e., Ajax calls (get, head or post) or PalmCall or the db8 wrapper APIs).

  • Call the Future's "then" method to set up a function to call when the Future has a result.

  • Do something that will cause the Future's result to get set.

Error Handling

If the function specified in a "then" throws an exception, the exception is logged and stored in the Future. The next time the Future's "result" property is accessed, it is re-thrown. This allows your app to defer error handling to the end of the chain, if desired. Note, however, that reading a Future's "exception" resets the mechanism that causes errors to be re-thrown.

Deferred error-handling example:

var libraries = MojoLoader.require({ name: "foundations", version: "1.0" });
var Future = libraries["foundations"].Control.Future;
var f = new Future("Hello");
f.then(function(future) {
  if (future.result == "Hello") {
      throw "Error";      
  }
  future.result = "Success";
});
f.then(function(future) {
  if (future.result == "Success") // Error is re-thrown on this access
  {
       Mojo.Log.info("In stage 2");
  }
  future.result = "Did stage 2";
});
f.then(function(future) {
  if (future.exception) {
      Mojo.Log.info("Stage 3 Exception");
      future.result = false;
  }
  else {
      future.result = true;
  }
});

Logged output:

Stage 3 Exception

Passing Error Functions

When calling "then", you can specify a function to run if the previous stage threw an error:

var f = new Future("Hello");
f.then(function(future) {
  if (future.result == "Hello") {
      throw "Error";
  }
  future.result = "Success";
});

f.then(function(future) {
  Mojo.Log.info("In stage 2");
  future.result = "Did stage 2";
},
function(future) {        // Pass this error handling function
  Mojo.Log.info("In error handler, error was "+future.exception);
  future.result = "Skipped stage 2";
});

As an alternative to this (since reading reading a Future's "exception" resets the mechanism that causes errors to be re-thrown), you could have the following code:

f.then(function(future) {
  if (future.exception) {
      // do some error recovery
  }
  future.result=true;
});

Creating a Future-wide Error Handler

The "onError" function establishes a Future-wide error handler that is invoked for every thrown error.

For example:

var f = new Future("Hello");
f.onError(function(future) {
  Mojo.Log.info("In error handler, error was: "+future.exception);
  future.result = true;
});
f.then(function(future) {
  future.result;
  throw("1");
});
f.then(function(future) {
  future.result;
  throw("2");
});
f.then(function(future) {
  future.result;
  throw("3");
});

Logged Output:

In error handler, error was: 1
In error handler, error was: 2
In error handler, error was: 3

Guidelines for Implementing Futures

  • "then" functions should read the Future's "result" property before doing anything.

    This allows errors to propagate correctly (see Error Handling).

  • "then" functions should always set the Future's "result" property.

    They should do this either directly, or as the result of a callback. It should do this exactly once, no matter what path the code takes.

  • Use a Finite State Machine (FSM) design.

    Imagine your Future chain or sequence as a Finite State Machine (FSM). Map out the states and state transitions ahead of time, and write your code accordingly.

    If your FSM has branches or cycles you can use a Future's "nest" method to handle these conditionally. It is not recommended you use "then" handlers for the same Future at different levels to do this.

  • Implement a flat hierarchy.

    Because their control flow is linear and declared up front, flat hierarchies are easier to understand than deep ones. Avoid making and using more Futures than are necessary.

    Attaching additional "then" handlers in nested functions is not recommended -- the execution order and response of "then" handlers at different levels is hard to anticipate.

  • Always access a future "result" before setting it again.

    Always read a Future's result before setting a new value. If the previous step resulted in an error, it aborts immediately and prevents unnecessary further processing.

    Always read a Future's result before using "nest" to tack on another step. These two should be done together in a "then". The one exception is if the "nest" is the first step in the sequence and there is no initial value.

  • Utility functions should return new objects.

    When designing utility APIs that use Futures, try to have them return new Future objects rather than accepting a client-provided Future object as a parameter. This gives more control to the caller and avoids letting the utility function clobber Future flow or make any assumptions.

    If you think of utility methods as complex and unrelated sub-sequences (see "nest" above), expecting them to return new Futures and connecting them with "nest" seems very logical.

    If you are writing a utility method and it needs to execute a conditional or branch, you should do one more step: since loops and branches usually mean deferred attachment of "then" functions, you should wrap this future sequence in a single outer future to return to the caller. This way, when the caller inevitably attaches "then" functions early, they will not break your private inner sequence.

  • Name your "then" functions.

    If your Future sequence has many steps, it could be helpful to name your "then" functions rather than leaving them anonymous. It is easier for someone else to follow a list of named steps than a long sequence of unrelated functions.


Future Properties

  • exception -- Contains the Future's exception, if there is one. Reading or writing this property is the same as accessing it with "getException" or "setException".
  • result -- Contains the Future's result. Reading or writing this property is the same as accessing it with "getResult" or "setResult".

Future Methods

  • callback -- Provides for a standard callback mechanism.
  • cancel -- Cancels any pending "then" stages.
  • getException -- Gets the Future's most recent exception captured in a "then" or "now" function.
  • getResult -- Get the Future's "result" property value.
  • nest -- Nest a Future inside the current Future.
  • now -- Calls the scope and function immediately in the Future's scope.
  • onError -- Defines a Future-wide error handler.
  • setException -- Sets the Future's "exception" property.
  • setResult -- Sets the Future's "result" property.
  • status -- Returns the Future's current status.
  • then -- Register a scope and function for execution when a Future has a result or exception set.
  • whilst -- Provides a Futures looping construct.

Code Sample Notes

The code samples for the "nest" and "then" methods require the following db8 database set-up:

// 1. Load and reference required libraries

var libraries = MojoLoader.require({ name: "foundations", version: "1.0" });
var Future = libraries["foundations"].Control.Future;
var DB = libraries["foundations"].Data.DB;

// 2. Create a db8 kind object

var testKind = { name: "dbtest:1",  owner: "com.palm.foundmain",
  indexes: [ //** create indexes
      {name:"name", props: [{name: "name"}]},
      {name:"profession", props:[{name: "profession"}]}   
  ]
};

// 3.  Create 5 test data objects

var testData =  [ { _kind: testKind.name, name: "Mark", age: 40, profession: "engineer" },
  {  _kind: testKind.name, name: "Yvette", age: 36,  profession: "trainer" },
  {  _kind: testKind.name, name: "Lenny", age: 45, profession: "engineer" },
  {  _kind: testKind.name, name: "Homer", age: 51, profession: "safety inspector"},
  {  _kind: testKind.name, name: "Marge", age: 48, profession: "homemaker" }
];

// 4. Use db8 JavaScript wrapper API calls to create the kind and test objects (no error checking)

DB.putKind(testKind.name, testKind.owner, testKind.indexes);
DB.put(testData); 

The above code creates a db8 kind and then puts 5 JSON data objects of that kind into db8 storage using db8 JavaScript wrapper API calls. See the db8 documentation for more information.


callback

Wraps and returns the scope and function pair in a new function that requires no arguments. Generally, this is used to interface the Futures mechanism to the common function callback schemes that Ajax and HTML5 databases use.

Syntax

    Future.callback(scope, func);

Parameters

Argument Required Type Description
scope No any object Call scope
func Yes Function Function to call.

Returns

None.

Example

In this example, a callback function is used in a "setTimeout" call.

var libraries = MojoLoader.require({ name: "foundations", version: "1.0" });
var Future    = libraries["foundations"].Control.Future;
var f = new Future();

setTimeout(f.callback(this, function() {
  f.result = "passed";
}), 100);

f.then(function(future) {
  Mojo.Log.info("In then, f.result="+f.result);
});


cancel

Cancels any pending "then" clauses. The Future is marked as canceled. It is an error to set a new result or "then" for a canceled Future.

Syntax

Future.cancel();

Parameters

None.

Returns

None.

Example

var libraries = MojoLoader.require({ name: "foundations", version: "1.0" });
var Future = libraries["foundations"].Control.Future;
var f = new Future();
f.then( this, function(future) {
  future.cancel();
  future.result="DONE";
}
).then( this, function(future) {
  Mojo.Log.info("Should never get here");
}
);


getException

Gets the Future's most recent exception captured in a "then" or "now" function.

Syntax

Future.getException();

Parameters

None.

Returns

Exception value.

Example

var libraries = MojoLoader.require({ name: "foundations", version: "1.0" });
var Future    = libraries["foundations"].Control.Future;
var AssertUtils = libraries["foundations"].Assert;
var f = new Future("Hello");

f.then(function(future) {
  future.exception = 2;
});

f.then(function(future) {
  var e = future.getException();
  AssertUtils.requireEqual(e, 2);
  future.setException(3);
});

f.then(function(future) {
  var e = future.getException();
  AssertUtils.requireEqual(e, 3);
  future.result = "Passed";       
  Mojo.Log.info("future.result = "+future.result);
});


getResult

Get the Future's result property value. Calling this does not stop the future's exception (if any) from being re-thrown.

Syntax

Future.getResult();

Parameters

None.

Returns

Future result value.

Example

var libraries = MojoLoader.require({ name: "foundations", version: "1.0" });
var Future    = libraries["foundations"].Control.Future;
var AssertUtils = libraries["foundations"].Assert;    
var f = new Future(1);

f.then(function(future) {
  AssertUtils.requireEqual(1, future.result);
  future.result = 2;
});

f.then(function(future) {
  AssertUtils.requireEqual(2, future.getResult());
  future.setResult(3);
});

f.then(function(future) {
  AssertUtils.requireEqual(future.result, future.getResult());
  future.setResult("passed");
});


nest

Nest a Future inside the current Future. This is useful when one Future contains many other Futures (for example, when a Future is created, which contains a number of database operations, each of which returns a Future). When an inner-Future completes, any results it contains propagate to the outer-Future.

Nests are useful as a way to abstract a complex and unrelated sub-sequence from the main sequence. They ensure that the main sequence always resumes upon sub-sequence completion, whether or not the sub-sequence resulted in an error or valid result.

Syntax

    Future.nest(innerfuture);

Parameters

Argument Required Type Description
innerfuture Yes Future Future to nest within the current future.

Returns

None.

Example

Do a series of db8 database finds, each of which returns a Future, and aggregate the results. See "Code Sample Notes" above to see how this code sample is set up.

// Allocate Future
var libraries = MojoLoader.require({ name: "foundations", version: "1.0" });
var Future = libraries["foundations"].Control.Future;
var DB = libraries["foundations"].Data.DB;
var f = new Future("hello");

// Retrieve and aggregate 3 records from db8 storage
var result1, result2, result3;
f.nest(DB.find({ "from": "dbtest:1", "limit":1, "where": [{ "prop":"profession", "op":"=", "val":"engineer"}]}).then( function(f)
{
  result1 = f.result.results[0];  
}));

f.nest(DB.find({ "from": "dbtest:1", "limit":1, "where": [{ "prop" :"profession", "op": "=", "val":"homemaker"}]}).then( function(f)
{
  result2 = f.result.results[0];
}));

f.nest(DB.find({"from":"dbtest:1", "limit":1, "where": [{ "prop" :"profession", "op": "=", "val":"trainer"}]}).then( function(f)
{
  result3 = f.result.results[0];
  f.result = {
      rec1:  result1,
      rec2:  result2,
      rec3:  result3
  };
  Mojo.Log.info("Final result="+JSON.stringify(f.result));
}));

Example Output

Final result=
{
  "rec1":{
      "_id":"++HJdTuywUsIfU6N",
      "_kind":"dbtest:1",
      "_rev":5074,
      "age":40,
      "name":"Mark",
      "profession":"engineer"
  },
  "rec2":{
      "_id":"++HJdTuz3o4ez4D3",
      "_kind":"dbtest:1",
      "_rev":5078,
      "age":48,
      "name":"Marge",
      "profession":"homemaker"
      },
  "rec3":{
      "_id":"++HJdTuyyN44nzZl",
      "_kind":"dbtest:1",
      "_rev":5075,
      "age":36,
      "name":"Yvette",
      "profession":"trainer"
  }
}


now

Calls the scope and function immediately in the current Future's scope. This behaves like "then" except the function is immediately executed rather than waiting for "result" to be set. Generally, this is useful if your app wants to capture any exceptions the "now" function might generate, and assign them to the Future.

Syntax

    Future.now(scope, func, errorFunc);

Parameters

Argument Required Type Description
scope No any Call scope
func Yes Function Function to call.
errorFunc No Function Error function to invoke on failure.

Returns

None.

Example

var libraries = MojoLoader.require({ name: "foundations", version: "1.0" });
var Future    = libraries["foundations"].Control.Future;
var f = new Future("Hello");

f.then(function(future) {
  Mojo.Log.info("In first stage");
  if (future.result == "Hello") {
      setTimeout(function() { Mojo.Log.info("100 ms passed"); future.result = "Success"; }, 100);
  }
});

f.now(function(future) {
  if (future.result == "Success" )
      Mojo.Log.info("First then finished");
  else
      Mojo.Log.info("Finished stage 2 before stage 1");
  future.result="Stage 2 done";          
});

f.then(function(future) {
  Mojo.Log.info("In stage 3 ");
  if (future.exception)
      future.result = false;
  else
      future.result = true;
});

Example Output

In first stage
Finished stage 2 before stage 1
In stage 3
100 ms passed

If the "now" was a "then", you would see the following output:

In first stage
100 ms passed
First then finished
In stage 3


onError

Used to define a Future-wide error handler. This overrides the default error handling in a Future (which passes the exception through to the next then clause). Instead, all errors are passed to the function defined with this call.

Syntax

    Future.OnError(func);

Parameters

Argument Required Type Description
func Yes Function Function to call when error occurs.

Returns

None.

Example

var libraries = MojoLoader.require({ name: "foundations", version: "1.0" });
var Future    = libraries["foundations"].Control.Future;
var f = new Future("Hello");

f.onError(function(future) {
  Mojo.Log.info("In error handler, error was: "+future.exception);
  future.result = false;
});


setException

Sets the exception for the Future.

Syntax

    Future.setException(value);

Parameters

Argument Required Type Description
value Yes any Value to set.

Returns

None.

Example

var libraries   = MojoLoader.require({ name: "foundations", version: "1.0" });
var Future      = libraries["foundations"].Control.Future;
var AssertUtils = libraries["foundations"].Assert;

var f = new Future("Hello");

f.then(function(future) {
  future.exception = 2;
});

f.then(function(future) {
  var e = future.exception;
  AssertUtils.requireEqual(e, 2);
  future.setException(3);
});

f.then(function(future) {
  var e = future.exception;
  AssertUtils.requireEqual(e, 3);
  future.result = "Passed";
  Mojo.Log.info("future.result = "+future.result);
});


setResult

Sets the Future's "result" property. Calling this does not stop the future's exception (if any) from being re-thrown.

Syntax

    Future.setResult(value);

Parameters

Argument Required Type Description
value Yes any Value to set.

Returns

Example

var libraries = MojoLoader.require({ name: "foundations", version: "1.0" });
var Future    = libraries["foundations"].Control.Future;
var AssertUtils = libraries["foundations"].Assert;

var f = new Future(1);
f.then(function(future) {
  AssertUtils.requireEqual(1, future.result);
  future.result = 2;
});

f.then(function(future) {
  AssertUtils.requireEqual(2, future.getResult());
  future.setResult(3);
});

f.then(function(future) {
  AssertUtils.requireEqual(future.result, future.getResult());
  future.setResult("passed");
});


status

Returns the Future's current status.

Syntax

string Future.status();

Parameters

None.

Returns

One of the following:

  • cancelled -- A pending result or exception was cancelled.
  • exception -- An exception is pending.
  • none -- No result, exception, or cancellation is pending.
  • result -- A result is pending.

then

Register a scope and function for execution when a Future has a result or exception set. You can register multiple "thens" and they are organized in the order registered. One "then" function is called per "result" or "exception" set on the Future.

The function registered in the "then" should take the form:

function myThenFunc(future) { .... }

The Future passed as the argument is the one that triggered the function call, allowing the same function to be used in different Futures. The function is executed in the scope passed. If the function throws any exceptions that are not handled, the Future passes them to subsequent "then" handlers.

Syntax

    Future.then(scope, thenfunc, errorfunc);

Parameters

Argument Required Type Description
scope No any object Call scope
thenfunc Yes Function Function to call.
errorFunc No Function Error function to invoke on failure.

Returns

None.

Example

The following example invokes the JavaScript wrapper API (which returns a future) for the db8 "find" (query) call once each in three different "thens". The final "then" aggregrates the results of all three find/tden calls.

var libraries = MojoLoader.require({ name: "foundations", version: "1.0" });
var Future    = libraries["foundations"].Control.Future;
var DB        = libraries["foundations"].Data.DB;

var f = new Future("hello");
var result1, result2, result3;
f.then(DB.find({ "from": "dbtest:1", "limit":1, "where": [{ "prop":"profession", "op":"=", "val":"engineer"}]}).then( function(f)
{
  result1 = f.result.results[0];
}));

f.then(DB.find({ "from": "dbtest:1", "limit":1, "where": [{ "prop" :"profession", "op": "=", "val":"homemaker"}]}).then( function(f)
{
  result2 = f.result.results[0];
}));

f.then(DB.find({"from":"dbtest:1", "limit":1, "where": [{ "prop" :"profession", "op": "=", "val":"trainer"}]}).then( function(f)
{
  result3 = f.result.results[0];
  f.result = {
      rec1:  result1,
      rec2:  result2,
      rec3:  result3
  };
  Mojo.Log.info("Final result="+JSON.stringify(f.result));
}));

Example Output

Final result=
{
  "rec1":{
      "_id":"++HJdTuywUsIfU6N",
      "_kind":"dbtest:1",
      "_rev":5074,
      "age":40,
      "name":"Mark",
      "profession":"engineer"
  },
  "rec2":{
      "_id":"++HJdTuz3o4ez4D3",
      "_kind":"dbtest:1",
      "_rev":5078,
      "age":48,
      "name":"Marge",
      "profession":"homemaker"
  },
  "rec3":{
      "_id":"++HJdTuyyN44nzZl",
      "_kind":"dbtest:1",
      "_rev":5075,
      "age":36,
      "name":"Yvette",
      "profession":"trainer"
  }
}


whilst

Provides a Futures looping construct. Every time through the loop, "conditionfunc" is executed. If it returns a "true" value, then "func" is called using Future.now().

Syntax

    Future.whilst(scope, conditionfunc, func, errorfunc);

Parameters

Argument Required Type Description
scope No any object Call scope
conditionfunc Yes Function Function executed every time through loop.
func Yes Function Function to call.
errorFunc No Function Error function to invoke on failure.

Returns

None.

Example

The following will execute the "now" function once, the "whilst" 10 times and, finally, the "then" once.

var libraries = MojoLoader.require({ name: "foundations", version: "1.0" });
var Future = libraries["foundations"].Control.Future;
var f = new Future("Hello");

f.now( function(f)
  {
      f.result = 0;
  }
  ).whilst(this, function(f)
      {
          return f.result < 10;
      },
      function(f)
      {
          f.result++;
      }
      ).then( function(f)
          {
              if (f.result == 10) {
              f.result = "Done";
          } else {
              f.result = "Error, should have been 10, was "+f.result;
          }
      }
);