Proper asynchronous with Dojo

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.

 

Leave a Reply