CHAPTER 14
Promises provide a standard implementation of handling asynchronous programming in JavaScript without using callbacks. One of the biggest side effects of using the callback pattern is how dreadfully ugly your code can get with only a few levels of callbacks.
Promises allow you to develop asynchronous scripts more easily than using callbacks. A promise represents a value that we can handle at some point in the future. Promises give us two main advantages over callbacks:
Let’s look at the following example:
Code Listing: 195
var p2 = Promise.resolve("foo"); p2.then((res) => console.log(res)); var p = new Promise(function(resolve, reject) { setTimeout(() => resolve(4), 2000); }); p.then((res) => { res += 2; console.log(res); }); p.then((res) => console.log(res)); |
In the first line, we create a promise and resolve it immediately. This demonstrates the second advantage. We can still call on the promise and get the value from it, unlike events. Next, we define a standard promise and have it resolve after two seconds. Our handler receives the value from the promise but cannot change the value of the promise itself. This is good in that our promise is immutable and always returns the same result when called multiple times.
The following is the output from the preceding example:
Code Listing: 196
foo 6 4 |
Up until now, we have only resolved promises. What about when we reject a promise? Consider the following code:
Code Listing: 197
var p = new Promise(function(resolve, reject) { setTimeout(() => reject("Timed out!"), 2000); }); p.then((res) => console.log(res), (err) => console.log(err)); |
This time, we are calling reject after our two second delay. We see that then()can also take in a second handler for errors.
The following is the output from the preceding example:
Code Listing: 198
Timed out! |
But wait, you can write this even more differently! Consider the following code:
Code Listing: 199
var p = new Promise(function(resolve, reject) { setTimeout(() => reject("Timed out!"), 2000); }); p.then((res) => console.log("Response:", res)) .catch((err) => console.log("Error:", err)); |
You can use the catch function off of the promise as well. Our output will be exactly the same:
Code Listing: 200
Error: Timed out! |
Let’s consider another example, when an exception happens. Consider the following code:
Code Listing: 201
var p = new Promise(function(resolve, reject) { setTimeout(() => {throw new Error("Error encountered!");}, 2000); }); p.then((res) => console.log("Response:", res)) .catch((err) => console.log("Error:", err));
|
Throwing an Error is the same as calling reject(). You are able to catch the error and handle it accordingly.
The following is the output from the preceding example:
Code Listing: 202
Error encountered! |
One nice thing about promises is that many synchronous tools still work, because promise-based functions return results.
Consider the following example:
Code Listing: 203
var fileUrls = [ 'http://example.com/file1.txt', 'http://example.com/file2.txt' ]; var httpGet = function(item) { return new Promise(function(resolve, reject) { setTimeout(() => resolve(item), 2000); }); }; var promisedTexts = fileUrls.map(httpGet); Promise.all(promisedTexts) .then(function (texts) { texts.forEach(function (text) { console.log(text); }); }) .catch(function (reason) { // Receives first rejection among the promises });
|
In this example, we are using an asynchronous call to load files. The elegance of this code is that then() will not be called until all of the promises have completed. However, if any of the promises fail, the catch() handler will be called.
Sometimes we don’t want to wait until all of the promises have completed; rather, we want to get the results of the first promise in the array to fulfill. We can do this with Promise.race() which, like Promise.all(), takes an array of promises. However, unlike Promise.all(), it will fulfill its returned promise as soon as the first promise in that array fulfills.
Code Listing: 204
function delay(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms); }); } Promise.race([ delay(3000).then(() => "I finished second."), delay(2000).then(() => "I finished first.") ]) .then(function(txt) { console.log(txt); }) .catch(function(err) { console.log("error:", err); });
|
Here, we are using a helper function, delay(), that will timeout after a certain amount of time. We create our promise passing in an array. Next, we simply dump out what happens.
The following is the output from the preceding example:
Code Listing: 205
I finished first. |
As you can see, the first promise that fulfills is what we get in our then() handler.