Dojo’s Deferred module provides a convenient way of managing asynchronous operations. If you’re new to deferreds, you can get a good introduction by reading our blog post and some tutorials on dojotoolkit.org: Getting started with Deferreds and Dojo Deferreds and Promises.

There are a few features of the then method on a promise that are important to understand and remember:

  • then always returns a promise
  • the promise returned by then resolves to:
    • the value returned by the callback passed to then
    • OR

    • if the callback returns a promise, the value that promise resolves to

Chaining operations with then

The fact that then always returns a promise allows us to easily chain multiple operations:

request('/some/url').then(function (response) {
    return parseResponse();
}).then(function (responseData) {
    processData(responseData);
});

The nice thing about this is that it works the same regardless of whether parseResponse is sync or async – if it returns a promise, the callback passed to the next then call will run when that promise resolves and receive the value it resolves to. If parseResponse returns a data value, the callback will run immediately and receive that value. This is convenient if parseResponse is a function outside of our control and we’re not sure if we can rely on it being synchronous. However, if we know for certain parseResponse is synchronous then the second then is unnecessary; the above example could simply be implemented as follows:

request('/some/url').then(function (response) {
    var responseData = parseResponse();
    processData(responseData);
});

Chaining a collection of asynchronous processes

Using this knowledge, we can write a loop that sequences an arbitrary number of asynchronous processes:

function doNext(previousValue) {
    var dfd = new Deferred();

    // perform some async logic; resolve the promise
    setTimeout(function () {
        var next = String.fromCharCode(previousValue.charCodeAt(previousValue.length - 1) + 1);
        dfd.resolve(previousValue + next);
    }, 50);

    return dfd.promise;
}

var promise = doNext('a');

for (var i = 0; i < 9; i++) {
    promise = promise.then(doNext);
}

promise.then(function (finalResult) {
    // 'doNext' will have been invoked 10 times, each
    // invocation only occurring after the previous one completed

    // 'finalResult' will be the value returned
    // by the last invocation of 'doNext': 'abcdefghijk'
    console.log(finalResult);
});

If you find yourself trying to run a sequence of asynchronous operations in a loop, either a for loop or using Array#forEach, you may find that the operations do not run in sequence, one after the other. This example demonstrates the correct way to ensure that they run in sequence.

The key to this is understanding the loop body, line 13. If you unroll the loop, you see that the loop is conveniently handling this logic for you:

promise = promise.then(function () {
    return doNext();
}).then(function (result1) {
    return doNext(result1);
}).then(function (result2) {
    return doNext(result2);
}).then(function (result3) {
   // ...
}).then(function (result9) {
    return doNext(result9);
});

Keep in mind that this is only necessary if each process is dependent on the results of the previous. The dojo/promise/all module is perfect for managing a collection of asynchronous process if:

  • you need to wait until all the processes complete
  • each process is unrelated to the others – they have no dependency on data or side-effects from other processes
  • the order in which the processes resolve is unimportant

Conclusion

Mastering promises is an important step in becoming proficient at functional testing with Intern! Keep an eye on the ES6 Promise API, which is what Dojo 2 implements.