Javascript Promises – Part III – Chaining Promise’s
In the previous parts, we have learned about Promise
and Promise
error handling.
In this part, we are going to learning about chaining Promise
‘s.
Let’s take a look at the following code:
var dogs = ['dog1', 'dog2', 'dog3', 'dog4', 'dog5', 'dog6']; function printName (name) { return new Promise(function(resolve, reject) { console.log('printName: ' + name); resolve(name); }); } function displayNames(names) { var currentName = names.shift(); // take on name out to process if (currentName) { // if not end of array printName(currentName).then(function(name) { console.log('printName succeeded: ' + name); displayNames(names); }).catch(function(name) { console.log('printName failed: ' + name); displayNames(names); }); } } displayNames(dogs);
In the example above, we are trying to print the list of dogs’ names in a consequence order, which means name after name, not at the same time.
As you already know, in Javascript, functions return immediately after it is called, and a moment later, the callback function is called when the job actually finished. This leads to a situation where we can’t do a list of jobs one after another.
Promise
give us the power to do just what we want by chaining promises together.
When promises are chained together, the Javascript engine will only call the second promise’s job when the first promise has been either resolved or rejected. That way the promise’s jobs will be executed one after another.
In the example above, we are using recursion to make the code work in consequence. However, using promise, we can make the code simpler and more expandable.
Let’s look at the easy version first:
var dogs = ['dog1', 'dog2', 'dog3', 'dog4', 'dog5', 'dog6']; function printName (name) { return new Promise(function(resolve, reject) { console.log('printName: ' + name); resolve(name); }); } printName('dog1') .then(function(name){ console.log('printName succeeded: ' + name); printName('dog2').then(function(name){ console.log('printName succeeded: ' + name); }); });
Instead of writing code like this, we can leverage the power of chaining Promise and rewrite the code as below:
var dogs = ['dog1', 'dog2', 'dog3', 'dog4', 'dog5', 'dog6']; function printName (name) { return new Promise(function(resolve, reject) { console.log('printName: ' + name); resolve(name); }); } printName('dog1') .then(function(name){ console.log('printName succeeded: ' + name); return printName('dog2'); }) .then(function(name){ console.log('printName succeeded: ' + name); });
By chaining the promises like above, we are able to print dog2
after dog1
. The key point is, instead of passing the resolve and reject function to printName('dog2').then()
, we can just return the printName('dog2')
as a Promise, and the next .then()
just do its job in exactly the same manner as if it was passed to printName('dog2').then()
. This works because Promise
provide us with the chaining syntax to make the code more comprehensive.
Imagine that instead of creating a chain like this p1 -> (p2 -> (p3 -> p4))
, we can now create a chain like p1 -> p2 -> p3 -> p4
, which functions exactly the same.
But what if we have a lot of dogs? What if the dog list is dynamically generated? We just can’t know what dogs are there to print. So we must work out a way to dynamically make promise’s chain.
Let’s look at the upgraded version:
var dogs = ['dog1', 'dog2', 'dog3', 'dog4', 'dog5', 'dog6']; function printName (name) { return new Promise(function(resolve, reject) { console.log('printName: ' + name); resolve(name); }); } var sequence = Promise.resolve(); dogs.forEach(function(name) { sequence = sequence.then(function() { return printName(name); }).then(function(name) { console.log('printName succeeded: ' + name); }).catch(function() { console.log('printName failed: ' + name); }) })
In the code above, we use a very interesting trick by writing var sequence = Promise.resolve();
, which basically creates an empty promise that has been resolved to use as the beginning promise of the chain. After this, we can add the next promise to the chain easily just by using sequence.then()
Another thing that worth mentioning here is that if we use for
instead of forEach
, be careful because the index variable will not be the same by the time the promise’s resolve function is called as intended when creating Promise
chain.
The code should look like this:
var dogs = ['dog1', 'dog2', 'dog3', 'dog4', 'dog5', 'dog6']; function printName (name) { return new Promise(function(resolve, reject) { console.log('printName: ' + name); resolve(name); }); } var sequence = Promise.resolve(); for (var i = 0; i < dogs.length; i++) { (function() { // define closure to capture i at each step of loop var capturedIndex = i; sequence = sequence.then(function() { return printName(dogs[capturedIndex]); }).then(function(name) { console.log('printName succeeded: ' + name); }).catch(function() { console.log('printName failed: ' + name); }) }()) // invoke closure function immediately }
Creating an array of promises
Instead of chaining promises together, we can also create an array of promises and wait for all the promises to finish by using Promise.all()
like this:
function printName (name) { return new Promise(function(resolve, reject) { console.log('printName: ' + name); resolve(name); }); } var promiseArray = [ printName('dog1'), printName('dog2')]; Promise.all(promiseArray).then(function(names){ console.log(names); }).catch(function(names){ console.log(names); })
Promise.all
will wait for all the promises to finish and then call the final resolve function with the list of PromiseValue
of resolved promises in array, as well as call final reject function with the list of PromiseValue
of rejected promises in array. In other words, if promise array has 5 promises, 3 of which succeeded and 2 of which failed, then the final resolve function will be called with a list of 3 names and the final reject function will be called with a list of 2 names.
Congratulations, you’ve made it!
I hope you understand what promise is and how it works now.
If my explanation doesn’t seem to work for you, you might want to try reading this very good tutorial here.
If you have any thoughts or questions, please share your comment below.
Cheers!