When you create a Promise you can chain asynchronous behavior by using the then(success,fail) method. The success and fail are callback functions invoked appropriately when the promise is resolved or rejected. Note that since ‘then(…)’ returns a promise, this technique is called promise chaining: p.then(…).then(….).
What I discuss in this post are two subjects
1. How to ‘stitch’ multiple promises together?
2. How to ‘splice’ in a value into a promise chain?
3. How to chain using a promise object as an argument?
What if in complex application you already have promises objects and want to create promise chains linking them to each other? How would you do this with ‘then’ methods? Amazingly, in most examples of using Promises on the web this simple scenario is never presented directly. Granted it doesn’t *seem* to be relevant.
Here is the source of a simple demo based on one of the Dojo Deferred examples. It is ‘normal’ promise chaining. I’m using this as the basis for the investigation presented here. The output displayed on page should be: completed with value: 75
The actual chaining ‘stitching’ is setupTrans().then(function(x){return action()}).then(outputValue);
<!DOCTYPE html> <html > <head> <script src="http://ajax.googleapis.com/ajax/libs/dojo/1.10.0/dojo/dojo.js" data-dojo-config="async:false"></script> <script> require(["dojo/when", "dojo/Deferred", "dojo/dom", "dojo/on", "dojo/domReady!"],function(when, Deferred, dom, on){ var balance = 100; function asyncProcess(x){ var deferred = new Deferred(); setTimeout(function(){ if(x === 'plus' || x === 'minus'){ x === 'plus' ? (balance += 25) : (balance -= 25); } deferred.resolve(balance); }, 1000); return deferred.promise; } function setupTrans(){ return asyncProcess('z'); } function debitAccount(){ return asyncProcess('minus'); } function creditAccount(){ return asyncProcess('plus'); } function outputValue(value){ dom.byId("output").innerHTML += "<br/>completed with value: " + value; } on(dom.byId("startButton"), "click", function(){ var debit = true; var action = debit ? debitAccount : creditAccount; setupTrans() .then(function(x){return action()}) .then(outputValue); }); }); </script> </head> <body class="claro"> <h1>Output:</h1> <div id="output"></div><p /> <button type="button" id="startButton">Start</button> </body> </html>
What is ugly is the inline function. Luckily I found the “Flattening Promise Chains” article where the author shows how to get rid of the function and simplify. However that article didn’t answer my question, how can you simply and directly chain promise objects. What the article showed is that you have to use the correct structure or design. Well, of course, but …
Answer
I thought this would be difficult. The Dojo documentation page never mentions that instead of the first ‘then’ argument being onFulfilled handler function it could be an actual promise object. But this is mentioned in specs such as the Promises/A+ specification. Or does it? The ‘Promise resolution procedure’ seems to mix compile time view with runtime, or I’m reading it incorrectly. Q documentation is very good on this. Will have to research further.
To directly chain separate promise chains the above code sample is modified as follows by just using an existing promise as the argument to another chains then(…) method. However, this can only be done using a promise returning function.
on(dom.byId("startButton"), "click", function(){ var debit = true; var action = debit ? debitAccount : creditAccount; setupTrans() .then(action) .then(outputValue); });
Or, to inline the decision:
on(dom.byId("startButton"), "click", function(){ var debit = true; setupTrans() .then(debit ? debitAccount : creditAccount) .then(outputValue); });
Chaining using actual promise objects inside the then(f,r) is made easy using the approach shown next, using “scalars”.
Chaining values into promise chains
While looking at this subject I thought about a situation where we want to directly insert a known value into the chain so that the next ‘link’ will use this as the prior handler’s return value.
We have a chain, and I want to put the great Answer in there. This won’t work: var p1 = promiseReturningFunction().then(…).then(’42′).then(….).
Sure you can wrap ’42′ in an inline function then(function(){return ’42′}), but that is yucky. How about a function that does that:
/** return a function that returns the value */ function now(value){ return function(){return value;}; }
Now you can do: var p1 = async().then(…).then(now(’42′)).then(….);
Here is the prior source code with the changes. This is just some of the script changes. The output on screen is:
completed with value: 75
completed with value: Goodbye
/* ... */ function outputValue(value){ dom.byId("output").innerHTML += "<br/>completed with value: " + value; } function now(valueOrPromise){ return function(){return when(value);}; } on(dom.byId("startButton"), "click", function(){ var debit = true; var action = debit ? debitAccount : creditAccount; var promise1 = setupTrans() .then(debit ? debitAccount : creditAccount) .then(outputValue) .then(function(){ console.log("now do another promise chain..."); }); promise1.then((now('Goodbye'))).then(outputValue); });
Using promise object directly in then(f,r) by using now(x)
With the Promises implementation in Dojo, you can’t use promiseObject.then(anotherPromiseObject). And you can’t use promiseObject.then(when(anotherPromiseObject)). At least, I haven’t figured how to do it.
With the now(x) function shown above, this is easy. The code below will output:
completed with value: 75
completed with value: 100
completed with value: undefined
completed with value: finished both chains
/** return function that returns the value or apply Dojo's when(...) to return a Promise */ function now(valueOrPromise){ return function(){return when(value);}; } on(dom.byId("startButton"), "click", function(){ var debit = true; var action = debit ? debitAccount : creditAccount; var promise1 = setupTrans() .then(action) .then(outputValue) .then(function(){ console.log("now do another promise chain..."); }); debit = false; action = debit ? debitAccount : creditAccount; var promise2 = setupTrans() .then(action) .then(outputValue); promise1 .then(now(promise2)).then(outputValue) .then(now("finished both chains")) .then(outputValue); }); });
Groovy language has Promises too
GPars brings Promises to the Groovy language.
Further reading
- Flattening Promise Chains
- A Deeper Dive Into JavaScript Promises
- How-to Compose Node.js Promises with Q
- Promise Anti-patterns
- dojo/when
- q
- General Promise Resources
- Promises/A+
