In part one of this series, I introduced the concept of Promises, describing how they are created, how they become fulfilled or rejected, and how you can use the value from a promise once it is resolved. This week, I’m going to expand on the subject of retrieving the values and introduce chaining promises, which is a way of linking promises together that may or may not be dependent upon one another.
Promise Syntactic Sugar
This was the final example from the previous article:
I touched on the fact that using catch was equivalent to using a single then with two arguments, one for the fulfillment of the promise, and the other for the rejection:
I’m presenting this to demonstrate that catch is really just syntactic sugar. Chaining promises can get really complicated because there are many ways to write the same flow:
I never use the 1st or 3rd style of writing promises, but you may run across code that only uses then. Just remember that if then has two arguments, the second argument is called if the promise rejects or throws an error.
A promise should be limited to the minimal amount of logic in order for that promise to either resolve or reject. For example, imagine that we had to fetch some data from one external source, process it, and then post it back to some other source. For example, something like this:
We could write it this way, but now there are two operations (processJson and postJson) that can resolve or reject the promise. Instead, we should break them up so that they are clear independent steps in a flow:
Writing it this way almost reads like a sentence. First fetchJson then processJson then postJson. The then and catch function calls return a new promise that is fulfilled/rejected by the result of the function passed into the then or catch. The return value becomes the fulfillment value, a thrown error is the rejected value.
Interweaving then and catch
As I mentioned above, chaining can get a little complicated. For example, you can interweave multiple then and catch calls:
In this example, we try to fetch some content. If we are able to fetch it, we process it, otherwise we log an error and return a user-friendly error message as the content. We then try to locate the element with the id content, and set its innerHTML to the content. If an error is thrown by that, we’ll log a message indicating we couldn’t find the element.
Note that if you have consecutive then promises followed by a catch promise, an error thrown in any of the then promises will usually be passed to the catch. The exception to this is something I will talk about in the next section.
Promises May Not Execute the Way You Expect
So far in these articles, I’ve only chained together promises where the next promise depends on the fulfillment value or error from the previous one. Imagine, however, that I chain some promises together without using a fulfillment value:
At first glance, it might look like we’ll wait 500ms, log done, and then wait another 250ms and log really done. This isn’t what happens, however. We’ll see really done get logged after 250ms and then done get logged after another 250ms. What is going on here?
The function you pass to either a promise constructor, a then, or a catch executes as soon as it is able to do so. So in this example, both setTimeout calls happen immediately at the same time since the second doesn’t depend on the first. The really done appears first since its timeout is shorter, followed by the done.
If a promise requires an input value from the previous promise in order to be fulfilled, it won’t start executing until the previous promise has been fulfilled. Otherwise, it will begin executing immediately. The then syntax can be really confusing sometimes due to this, but later in this series I’ll talk about ways to clean up promise flows to make them more readable.
This week I expanded on promises by talking about promise chaining. I talked about how catch is really just syntactic sugar and how there are lots of different ways to chain promises and get the same result. I discussed interweaving then and catch and how promises in a chain execute as soon as they are able to, possibly leading to executions that don’t happen in the order you might expect.
Next week I’ll talk about async and await, which we can use to clean up promise flows and make them much easier to follow.