JavaScript Promise — Basics
Explained with good use of example code and diagrams.
Top Level of Abstraction
Like most of the things in JavaScript language, a Promise is an object. The MDN web docs describe it very well.
“A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action’s eventual value or failure reason.” — MDN web docs
In other words, at the time of creating a Promise, a long-running process can be started, and then the main program flow continues. The newly started process runs in parallel with the main program flow. What happens with the value returned by this long-running process can be defined before the returned value is known.
When the long-running process finishes, it either calls
- resolve() which resolves the Promise with a value, or
- reject() which rejects the Promise with a reason.
You define under what circumstance they are called at the time of creating the Promise. The Promise object makes sure that only one of the above functions is called and it is called only once.
Success and failure handlers are attached to a Promise by the developer using then() method. These handlers are called when the Promise is either fulfilled or rejected.
A Promise object can be in one of 3 states:
- pending: initial state
- fulfilled: operation completed successfully (resolved)
- rejected: operation failed
Diving Deeper
Have a look at the code snippet below. Start at line 15 where the Promise is created. The code is also available here:
https://gist.github.com/VYV7/63de8140448a1501e0bdac6a42cb8023
Line 15. A Promise object is created by the promise constructor — Promise(). Promise constructor takes one argument — an executor function defined by you. The executor function, in this case promiseExecutor(), is defined at line 5.
Line 5. This executor function, in turn, takes two arguments — functions.
- 1st function is called if things went well
- 2nd function is called if things went wrong.
The executor function is executed right away and this is where you start an asynchronous process. An asynchronous process could be an HTTP request. To simulate such a process I used setTimeout().
Very briefly, setTimeout() calls a function passed as the first argument after a delay passed as the second argument.
setTimeouot( function, delayms )
So we create a new Promise by calling its constructor which in turn calls the executor function. The executor function calls the delay function, setTimeout(), and immediately returns letting the delay function run in the background.
Promise() -> promiseExecutor() -> setTimeout()
While the delay function is running in the background, the main program flow continues. We call consol.log() at lines 16 and 17 and then define what we want to do after the delay (after the long-running process finished).
Line 23. We define what resolve() and reject() do, using then() method of the Promise object which we’ve just created. Generally, it goes like this:
Promise.then( onResolved(), onRejected() )
So, again, then() method takes two functions as arguments.
- The first one corresponds to resolve() and
- the second one corresponds to reject().
And again, resolved() and reject() are called from the executor function — promiseExecutor(), after a delay (after the long-running process finished).
The diagram below shows the Promise flow — have a look. I believe that if you can’t visualize something, then you don’t know it. Once you get that mental picture in your head then it sticks for much longer.
This diagram also becomes very useful when we start thinking about Promise chaining and error handling. I will describe that in a separate article.
Generally, Promises returned by one then() method can be handled by another one in the chain.