Not long ago, I was not really dealing as I should with asynchronous development in javascript. For instance, when I had to launch several asynchronous process and wait for them all to all complete, I was using a counter and decreasing it in the callback method until 0. Coming from the Java world and being use to synchronize/join/notify which make this so easy, I thought it has to be something better than this. And of course like always with Dojo, there is.
The solution lies in the dojo/Deferred, dojo/promise/Promise and dojo/promise/all classes.
I won’t explain again the basics here, since it is already done perfectly fine in the documentation, I just want to give some examples when dealing with a lot of asynchronous function and waiting for them to complete, before doing something else. The first situation where I wanted this was when I had to retrieve a bunch of items from a repository in IBM Content Navigator, and then do something with the items fully fetched.
Wait for several asynchronous process
Here is my classic syntax when waiting for a several processes.
var allPromises = []; for (var i in items) { var item = items[i]; var promise = doSomethingReturningAPromise(); allPromises.push(promise); } // Wait for all promises all(allPromises).then(lang.hitch(this, function() { this._AllItemsRetrieved(); }));
And that’s it, I might not be really shorter than using a counter in a callback but it is a lot easier to read and understand.
How to use Deferred/Promise with classic callback
A problem you can have, especially when working with other framework, in my case IBM Content Navigator Javascript Model, is that they are still using classical callbacks in their method. I am not criticizing, I actually think it is a good thing because this way the framework isn’t tight to Dojo, but in our case it makes the task harder. What I usually do is create a function to convert it to another one returning a promise:
setItems: function(items) { var allPromises = []; for (var i in items) { var item = items[i]; allPromises.push(this._retrieveAttributes(item)); } all(allPromises).then(lang.hitch(this, this._continueSetItemsAfterRetrieval)); }, /** * Use to associate the ecm.model.ContentItem.retrieveAttributes to a deferred object * @return {dojo.promise.Promise} a promise with the item as parameter when resolved */ _retrieveAttributes: function(item) { var def = new Deferred(); item.retrieveAttributes(function(){def.resolve(item)}, false, true, function(e) {console.log(e);def.reject(item);}); return def.promise; },
As you can see, I use a function to convert the classic callback to a promise. The usual callback actually resolves the promise. Like this , _continueSetItemsAfterRetrieval will be called only when all callback have been called. It could indeed be extract in a helper function doing always the same thing, something like:
/** * Convert method using a standard callback as first argument to a function returning a promise * @param {any} the item to called the fonction on * @param {function} f the function to call on the item, taking the callback as first argument * @param args arguments to pass to the function f */ convertToPromise: function(item, f, args) { var def = new Deferred(); args.unshift(function() {def.resolve();}) item.f.apply(item, args); return def.promise; }
Not tried yet, use it at your own risk, I will correct it when I have time to try it. It is just an idea to simplify my developments for later.