Working with async operations is more complex than normal sync code mainly because:
- You need to be able to wait for the results to be generated, for example if you need to communicate with a distant server in order to produce them.
- You also need to be able to react to failure conditions that are outside of your control, the server may be down or unable to process your request.
The usual way to tackle this is using callbacks. You pass a function that will be called with the generated result when the work is done. If anything went wrong, you need to inform the error through a flag in the callback. An usual async function will look like:
var async1 = function(pa,pb,callback) { setTimeout(function(){ // do async work with pa and pb, generate result and then... if( error && callback ) { callback( null, 'something went wrong' ); return; } // return result if( callback ) callback( result ); },1000);};var async2 = function(callback) { ... };
And you will use it as:
async1(pa,pb,function(result,error){ if( result ) { // Do something after we are done } else { // Handle error condition } });async2(function(result,error){ ... });
This is fine and it works. But when you try to compose code using this pattern things gets out of hand quickly. The simplest way to see the hidden complexity is try to wrap one of this functions to be able to process the result before it gets to the hands of the caller:
var asyncWrapper = function(pa,pb,callback) { async1(pa,pb,function(result,error){ if( callback ){ if( result ){ // process and generate new result callback( myResult ); } else { callback( null, error ); } } });};
We need to forward the error condition from the wrapped async function so the caller of asyncWrapper can also react.
The promises pattern
The promises pattern is a formalization of async operations handling that allows the creation of composable code, simplifying these kind of scenarios. The idea is simple, instead of passing a callback in the parameters you return a Promise object from your async operation that represents the future value to be generated.
var promisedValue = asyncOperation$(pa,pb);
The promise will be resolved later on time when the value is ready, or it will be rejected if something went wrong at some point. To be able to get the value you attach a callback using the done function that will be issued when the operation is done:
promisedValue.done( function(value){ // the operation is done, value is ready to be used});
There is no need to ask if there is actually a value inside the done callback because it will only be called if the operation is successful. To handle error conditions you add a fail callback like:
promisedValue.fail( function(error){ // handle failure condition});
We do not need to create a named variable for the promised value, and both done and fail function will return back the promise so you can chain the calls directly like:
asyncOperation$(pa,pb).done( function(value){ // the operation is done, value is ready to be used}).fail( function(error){ // handle failure condition});
Til here, we didn’t gain much, the code looks similar to the one using callback in the parameters. An initial small win is that now callback are not longer mixed with other function parameters.
But the formalization of the promise object is the key to attach extra functionality that will allow us to easily compose operations that uses this pattern.
Chaining (then)
The first important extension is the then function. When we created the asyncWrapper, we had the need to be able to process the generated value in an async operation before the user can use it. In the promises world, you can do it by attaching a filter that will be executed when the value is ready, let you create something new using it and pass that value along the chain of async operations. The function we coded before can be rewritten as:
var asyncWrapper = function(pa,pb) { return asyncOperation$(pa,pb).then( function(value) { // process and generate new result return myValue; });};
The then function takes a doneFilter, and wrap the promised value returning a new promise where you can later attach new callbacks. If we have used named variables for each promise, it would look like:
var asyncWrapper = function(pa,pb) { var promise1 = asyncOperation$(pa,pb) ; var promise2 = promise1.then( function(value) { // process and generate new result return myValue; }); return promise2;};
You can also return a promise yourself inside the filter allowing the piping or chaining of async operations. The newly created promise will also be rejected automatically if something went wrong in any of the operations before. We do not need to place any special error treatment in the asyncWrapper, the caller will be able to attach a fail callback without problems. If you ever need to filter the error condition to attach extra information, the then filter.
To showcase what then buys us, lets analyze an algorithm where we need to execute one async operation chained after the other and give the user the chance to know when both operations are done. Using the callback parameter we will do it like:
var chainedAsync = function(callback) { async1(pa,pb,function(result1,error1){ if( result1 ) { // Do something after async1 and then call async2(function(result2,error2){ if( result2 ) { // do something when async2 is done if( callback ) callback(myResult); } else if( callback ) callback(null,error2); }); } else if( callback ) callback(null,error1); });}
The same operation, using promises and then looks like:
var chainedAsync$ = function() { return async1$(pa,pb).then( function(result1){ // Do something after async1 and then call return async2$().then( function(result2){ // do something when async2 is done return myResult; }); });}
You can see how much clean the code looks now that we do not need to deal with error propagation because it is being done for us. We can also just use result1 and result2 because we know that the doneFilter will only be issue when things when right. And because chainedAsync$ is returning a promise, the user can continue to chain whatever needs to be done after the second async operation is done.
Understanding done and then
It is important to understand the difference between done and then. The done function adds a callback that will be issue when the value is ready to be consumed and returns the same promise so you can continue to work with it.
operation1$().done( function(value1){ // (a) // Do something when operation1$ is done}).done( function(value1){ // (b) // This will also be called when operation1$ is resolved.});
operation1$() => p1 |__ done -> (a) |__ done -> (b)
On the other hand, then introduces a filter that will let you process the generated value and return a new promise:
operation1$().then( function(value1){ // (a) return operation2$(value1);}).done( function(value2){ // (b) // This will be called when operation2$ is done.});
operation1$ => p1 |__ then -> (a) operation2$ => p2 |__ done -> (b)
Concurrent operations (when)
Another usual task when working with async operations is to issue several concurrent operations and do something when all of them are done. You can be loading different resources to generate your result for example. To be able to wait for jobs concurrently, we needed to do something along the lines of:
var concurrentAsync = function(callback) { // We are going to wait for async1 and async2 var count = 2; var results = []; var gatherResults = function(i,result,error) { results[i] = result; errors [i] = error; if( count-- == 0 ){ // Both are done... // do something here and return callback( myResult, myError ); } }; async1(pa,pb,function(result1,error1){ gatherResults(0,result1,error1); }); async2(function(result2,error2){ gatherResults(1,result2,error2); });}
We have an abstraction in jQuery that simplifies these kind of tasks: the $.when function takes an arbitrary number of promises and produces a new promise that will be resolved when all operations are done. If any of the concurrent promises gets rejected, the created promise will be rejected and your fail callback will be issued.
var myAsync$ = function() { return Cx.when( async1$(pa,pb), async2$() ).then(function( result1, result2 ) { // do something here and return return myResult; });}
One of the strength of promises is that once you start using promises, making composed async operations is a lot easier (once you understand how this works, obviously).
Creating promises (Deferred)
Til here, we have always used operations that were already returning a promise. To create a promise object jQuery exposes the Deferred object. You do it like:
var operation$ = function(pa,pb) { var dp = Cx.Defer(); // perform async work and then... setTimeout(function(){ dp.resolve( value ); // if anything went wrong dp.reject( error ); },1000); return dp.promise();}
You first create a deferred object using the Cx.Defer() constructor. When the operation is done and you have generated the value to be returned you call the resolve function. If something went wrong you trigger the fail using the reject function.
The last line uses the promise function to return a stripped down version of the Deferred object that do not expose the mutator functions resolve and reject.
Abstracting in the async world
We want to create a LightBox (a showcase box for a figure) using something along the lines of:
// Add Light Boxfigure.bounds$() .then(function(bounds){var lbb = Cx.Shape.CreateRectangle({ bounds:bounds.inflate(50,50), brush:...});var lightBox = Cx.Group.create([lbb,figure]);doc.add( lightBox );canvas.zoomToFit$() .then(function(){doc.commit$();});});
A first step to make this code more readable and, more importantly, to allow reuse of code is to modularize parts of the work in the same way you will do it in a sync world. Imagine we could avoid the need for
async operations, the code above will look like:
var bounds = figure.bounds();var lbb = Cx.Shape.CreateRectangle({ bounds:bounds.inflate(50,50), brush:...});var lightBox = Cx.Group.create([lbb,figure]);doc.add( lightBox );canvas.zoomToFit();doc.commit();
In this case, there is an abstraction screaming to be made:`
App.createLightBox = function(figure,margin) {var bounds = figure.bounds();var lbb = Cx.Shape.CreateRectangle({ bounds:bounds.inflate(margin,margin), brush:...});return Cx.Group.create([lbb,figure]);};
Now we can just code the above functionality like:
var lightBox = App.createLightBox(figure,50);doc.add( lightBox );canvas.zoomToFit();doc.commit();
The same thing can be done in the async world to keep the complexity as low as possible and to let others use the functionality we are creating. Promises are a great tool to allow you to easily refactor out reusable parts of your functions. We can create an async operation to create the light box like:
App.createLightBox$ = function(figure,margin) { return figure.bounds$() .then( function(bounds){ var lbb = Cx.Shape.CreateRectangle({ bounds:bounds.inflate(margin,margin), brush:...}); return Cx.Group.create([lbb,figure]); });}
And you can now rewrite the original as:
App.createLightBox$(figure,50).done( function(lightBox){doc.add( lightBox );canvas.zoomToFit$().done( function(){doc.commit$();});});
That using this indentation looks exactly the same as the sync version, even with the two async operations being performed:
var lightBox = App.createLightBox(figure,50);doc.add( lightBox );canvas.zoomToFit();doc.commit();
We are using the done function because we are not interested in chaining or creating a callable function. This may be the operation executed when you press a button so nobody will be waiting for the result. You could have used then without problems, and normally you will want to use then for each middleware function you are creating so your users can compose your functions with all other promise based operations.