JavaScript Promises - Understand JavaScript Promises by Building a Simple Promise Example
A step-by-step tutorial to make sure you fully understand how JavaScript promises work by building a promise from scratch
In this tutorial you will learn JavaScript promises by walking through building a JavaScript promise from scratch. This lesson is great for beginners to intermediate developers who are still learning the fundamentals of asynchronous JavaScript. The JavaScript promise example you will build is meant to help you understand the basics of promises and asynchronous thinking — it is not intended to show the most optimized version of a promise.
You’ve probably seen something like this before:
fetch('/user/1')
.then((user) => {
/* Do something with user after the API returns */
})
The block of code in the .then()
waits until it receives the response from the server before it executes anything. This is called a Promise
. But don’t let the fancy name or the fact that there is asynchronous code intimidate you — a Promise
is just a plain old JavaScript object with special methods that allow you to execute code synchronously (it will do things in order even though there is a delay).
typeof new Promise((resolve, reject) => {}) === 'object' // true
Let me reiterate (because this is something that was difficult for me to grasp when I first learned promises), a Promise
is just an object. To be able to wait on the server and execute the code in the .then()
chain after the response, you MUST return a Promise
object. This is not something functions get out of the box. Behind the scenes, the fetch function is doing something like this.
const fetch = function(url) {
return new Promise((resolve, reject) => {
request((error, apiResponse) => {
if (error) {
reject(error)
}
resolve(apiResponse)
})
})
}
The fetch()
function makes an http request to the server, but the client doesn’t know when the server will send back the results. So JavaScript begins executing other unrelated code while waiting on the server to return with the response. Once the client receives the response, it initiates the execution of the code in the .then()
statements by calling resolve(apiResponse)
.
Now let’s take a closer look at how the Promise
actually allows you to do this.
NOTE: This version of a Promise is for educational purposes only. I’ve left out some of the more advanced features and distilled it to its core functionality.
I’ve named it PromiseSimple
so it won’t clash with the native Promise
in case you want to copy and paste it into your Chrome console. Our promise implementation has a constructor
, 2 public methods that you may be familiar with then()
and catch()
, and 2 internal methods onResolve()
and onReject()
.
When you create a promise, you do so like this new Promise((resolve, reject) => {/* ... */})
. You pass it a callback function which I’ve named executionFunction
in the constructor. The execution function takes a resolve
and reject
which map to the internal onResolve()
and onReject()
function. These are the functions that will be called when the fetch calls the resolve or reject.
The constructor also creates a promiseChain
array and handleError
function. When a series of .then(() => {})
are added, it pushes each function onto the promiseChain
. When a user calls catch(() => {})
, it assigns the function to the internal handleError
. Notice that the then()
and catch()
function return this;
. This allows you to chain multiple then()
’s since you’re returning the object itself.
NOTE: In the native
Promise
, thesethen()
andcatch()
functions actually return anew Promise
themselves, but for this simple scenario, I’ve only returnedthis
. In addition, there can be multiple.catch()
blocks and they can be chained as well and aren’t required to come at the end of the.then()
chain.
When your asynchronous function calls resolve(apiResponse)
, the promise object then begins executing onResolve(apiResponse)
. It iterates through the entire promiseChain
by removing the function at the front and executes it with the most recent value saved in storedValue
. It then updates storedValue
to the result of the most recent execution. It will execute these functions in order. This creates the synchronous promise chain.
This loop is wrapped in a try/catch
block. This is special JavaScript syntax that looks for errors. If your asynchronous function calls reject(error)
or your try/catch
recognizes an error, it will then be passed to the onReject()
method which calls the function that you passed to .catch()
.
Putting it all together with a more practical example:
If you found this article helpful, please tap the 👏. Follow me for more articles on React, Node.js, JavaScript, and open source software! You can also find me on Twitter or gitconnected.