06 Jul 2018 ~ 6 min read
Javascript Promises: An In-Depth API Guide
“Write down the syntax for promises on this sheet of paper”, is enough to give nightmares to a lot of junior and even mid-level javascript developers. I have seen people going blank and sit for minutes together trying to put their mind around creating a simple promise and a callback.
I am sure you would have read many articles about promises, but I promise (pun intended..) you that this one will show you the parts which you have missed. With no time to waste, let’s jump in.
P.S: If you know the basics just skip to the sub-heading called ‘Promises with multiple then methods’ or if you are an expert jump to ‘Order of execution of promise blocks’, you might see some surprises there.!!
Definition
Let’s keep the books and docs away for some time and put things in perspective. Promises are basically javascript objects used asynchronously.
In a synchronous execution, you don’t need states, per se. Your code either successfully execute or fail to execute. In asynchronous code, we execute, wait for callbacks and decide if its success or failure and continue with the synchronous code execution.
On the same lines promises have three states:
Pending, Resolved and Rejected.(I am sure the names are self-explanatory here).
Pending:
Every promise created remains indefinitely in a pending state, unless it resolved or rejected. Period.!! This is the absolute truth. No arguing with it.
Resolved:
When the promise’s resolve method is called the promise takes up a resolved state. Resolve is essential for a promise to move on to execute its then method.
Rejected:
As the name suggests the promise moves on to rejected state from pending state if its rejected. And goes on to execute the catch method, where you can extract the error if there is any.
Syntax
Since we have moved on from ES5 to ES6, I am just gonna straightaway use the ES6 Syntax.
Basic Promises Syntax
Note: Here, the then method accepts both resolved and rejected callbacks, but catch accepts only rejected callbacks. In practice, always use the then method for handling resolved callbacks and catch for rejections to keep things cleaner and for proper funnelling of errors at a single exit point.
This syntax is pretty clean in ES6 than in ES5. I vaguely, remember mixing resolve and reject in the same then method, resulting in some very ugly and unreadable code. So, now we know how the syntax works in Promises. Let’s jump into different ways of using promises and unravel the secrets behind them.
Basic Promise With A Single Then Method
This is probably the simplest form of Promise you will ever come across and is the key to understanding how Promises really work. We have a simple promise constructor and when the promise is resolved it executes the resolved-callback/handler in the then method. The resolved handler is not called until the promise is resolved, in other words, it waits for the promises to be resolved. This is the key to the asynchronous nature of the Promises.
Basic Promises with a single then method
Once the resolve method of the Promise constructor is called the resolve handler picks up the resolved value as an argument and prints it. Interestingly the then method doesn’t always behave this way in a Promise. Let’s see more of thens in the next section.
Promises With Multiple Then Methods
Now that we have understood how Promises generally work, let’s look at how the then method works. As said above the then method accepts both resolve and reject handlers, but for the sake of simplicity, we are just going to use resolve handlers along with the then method.
Then method following a Promise constructor
Then methods following a Promise constructor or a Promise object doesn’t execute the resolve/reject handler until the Promise resolves/rejects. The then method attached to the Promise constructor is the first handler to get invoked on resolve/reject of the Promise. (Refer to the image above)
Then method following another ‘then’ returning a value/reference
The then methods following other then methods have different behaviour for promises and generic methods. The seemingly-synchronous behaviour is when the previous then the method doesn’t return any promises. Looking at the example below,
Then methods executing without return value
Once the promises are resolved the then methods linked to the promises start executing one after the other, here you can add any number of thens and it can go on forever. Literally forever. This is similar to a code inside a really long function, executing one after the other. Although this is asynchronous by-nature, this can be a bit confusing for beginners.
Note: one interesting behaviour of this is that when you do not return anything from the previous then method, x is undefined.
Then method following another ‘then’ returning a Promise
When a then method returns a promise in a sequence, the execution is suspended until this promise resolved/rejected. This is otherwise called as ‘Promise Chaining’, let’s see about it in the next section.
Promise Chaining
This is the core of Promise usage and possibly something which you need to understand before you can understand concepts around Async/await and Redux-sagas. Let’s take a deep dive.
Let’s consider we have two promise blocks, (a Promise block is a Promise constructor followed by its own then methods). And we want one block to execute after the other, what do we do? We make use of the asynchronous behaviour of the then method.
When a then method returns a promise, the subsequent executions of the then are suspended until this promise block is resolved.
Let’s look at a more detailed example to understand further. In the following image, you can see clearly see three promise blocks and let’s understand how they form a promise chain.
Chaining promises
Explanation
- The promise1 is built with a constructor, and will automatically move to a pending state until resolved or rejected. At the instance of creation, this promise is still pending, which will get resolved after one second.
- Similarly, promise2 and promise3 will also be pending, which will get resolved after 2 and 3 seconds respectively. (we will see the order of resolutions in coming sections, don’t worry about it now)
- At the1st second, promise1 is resolved and the resolved value of ‘first’ appears in the resolve handler.
- The resolve handler returns another promise called promise2. Now the subsequent the thens following would have to wait till promise 2 is resolved.
- The promise2 executes all the attached then methods, before continuing to execute the promise1’s then methods. The value returned by promise2 block’s last then method appears in the next occurring then of the promise1 block.
- Now again the execution is suspended till promise3 is resolved before finishing the whole execution.
At first, the example might be a bit harder to understand, give it a good 10 minute read to wrap your head around it. Let it sink in before you move on to the next section.
Promise Pyramid or Nested Promises
The promise chain grows linearly from the top-down, but only problem in this approach is that we do not have access to the variables between different then methods or between different promise blocks.
Although there are different ways of achieving this, to keep it simpler we can use something called as Nested Promises. These nested promises will have access to variables from its outer scope, and are otherwise called as promise-pyramids, which will grow to the right. If not done correctly, this promise pyramid can eventually lead to something called as Promise-Callback Hell.
Let’s look at the problems accessing variables in promise-chaining below,
Problem with promise chaining
To ensure that all the consecutive promise blocks have access to all the parental scope variables, we can nest promises. Let’s look a simple two level nesting of promises to understand it better,
Nested promises
In the nested promises, you can see that promise2’s then method resolves and prints both the value from the outer scope and the inner scope.
A very important point to keep in mind is that during nesting sometimes we forget to return the nested promises, which will lead to an anti-pattern where your resolves/rejects will not bubble up to the top. Always!! I say always return a promise when its chained or nested.
( You can try rejecting the promise2 and verify whether catch method successfully catches the error or not, take it as a challenge - with and without a return statement before promise2)
Sometimes nesting promises become inevitable which will inevitably lead to the callback hell we talked about, hence to tackle this issue ES7 has come up with Async/await. Be sure to check it out when you have time.
Order of Execution of Promise Blocks
Once you have mastered the nesting and chaining of promises, its now time to understand how promise execution works. Many developers think that promises would always wait till they are called or made use of, but that’s just wrong understanding of promises. A promise would try to resolve/reject it self as soon as possible, if it can.
There are two important points to remember about the promise execution cycle in a normal promise block and in a chained/nested promise block.
- A promise once created races towards resolve/reject. If its resolved/rejected it changes its state to resolved and executes the attached then/catch method.
- In a promise chaining or nesting when you return a promise inside a then method, and if the returned promise is already resolved/rejected, it will immediately call the subsequent then/catch method, if not it will wait.
Let’s look at an example to understand better,
Order of execution of promise blocks
Explanation
- promise1, promise2 and promise3 are initialised and they are racing towards resolve/reject in time. Currently, it is zeroth second.
- In the1st second, all three promises are pending.
- In the 2nd second, promise2 has resolved.
- In the 3rd second, promise1 has resolved and the then handler is executed. In the then handler a resolved promise2 is returned. (a resolved promise holds resolved value and has a resolved state)
- Since promise2 is already resolved it immediately calls the next then method attached, without waiting.
- In the 3rd second the promise 3 is still pending because it needs two more seconds to be resolved.
- After 5th second all three promises are resolved.
Catching errors in Promises
Catching an error is pretty straightforward in promises, a catch method at the end of a promise block should do the trick. It handles both, when an error is thrown and when its rejected.
But throwing errors has its limitations in asynchronous coding, when we throw an error inside an async block of code it will never be caught. Alternatively, rejecting and returning a promise properly should work seamlessly without any issues and its considered the best practice.
Also, you can reject a promise in any of its attached then methods by simply returning a Promise.reject(customErrorValue);
Promise.all() vs Promise.race()
Promise all resolves/rejects all the promises that are passed as an iterable before calling the then handler with an iterable result, whereas Promise race condition resolves/rejects the fastest promise that it can resolve/reject. Read more on the official docs.
Anti-patterns
There are many anti-patterns which you can avoid in a promise, but the main culprit is not returning promises and values properly. In a synchronous sequence of then methods following a Promise block, if a value is not returned from the previous then method it will not appear in the next occurring then.
Then methods with no-returns
Similarly, when we use a promise chaining, we need to return the promises. When we don’t return the promises, the consecutive then methods will not realise that it needs to wait for a promise in the sequence and simply assumes it as another line of code to be executed, there by losing its async nature.
Promise chaining with/without return statement
Note: Only in the promise constructor block you do not have to explicitly return a resolve/reject. Apart from that, in the consecutive then methods, every value and promise chained needs to be returned, for accessing it further down the block.
Bonus Coding Challenge
If you are interested in a Promise challenge, you can continue reading this para. Let’s assume that we have a for loop that prints 0 to 10 at random intervals (0 to 6 seconds). We need to modify it using promises to print sequentially 0 to 10. For example, if 0 takes 6 seconds to print and 1 takes two seconds to print, then 1 should wait for 0 to print and so on.
Conclusion
Understanding Promises is the first step towards understanding Async/await and solving challenges faced in Promises using the ES7 alternatives. If you think that this was worth your time follow me on GitHub.