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; } } );