JavaScript Promises

What is a promise

A promise is a JavaScript object that represents a value we may get in the future. Imagine we’re using fetch to get some data from an API. When we run fetch, we don’t get the data immediately, instead the fetch function returns a promise. We can tell the promise what it should do when it gets the real data (when it resolves) or when it fails (rejected).

States of a Promise

Promises can have three stages: pending, resolved and rejected. All promises start their lifecycle in the pending state. Eventually they’ll either get resolved or rejected. Note that any particular promise can only have two of the three states in their life, they either go from pending to resolved or rejected and they stay there forever. Once a promise is resolved or rejected, it stays at that state forever.

Creating a Promise

Most of the times, you don’t need to create a promise. Usually the promise is ready for you to consume. But in case you wanna create your own promise, here’s how to do it:

We use the JavaScript Promise constructor to create a new promise. This constructor accepts a function as its argument. We call this the executer function. The executer function accepts two arguments that are callbacks provided by the JavaScript language. The first argument (resolve) is called when the promise is resolved and the second one (reject) is called when the promise is rejected.

Consuming a Promise

Every promise object in JavaScript has access to a few methods. Two of the most important methods that every promise object has are then() and catch().

then() Method

Every promise object has a then() method. This method accepts one or two functions as its arguments. The first (or only) argument of the then() method will be called when the promise is resolved and accepts one argument which is the data that the resolve() function sends to it.

The second (optional) argument of the then method is a function that is called if the promise is rejected and accepts one argument which is the error that the reject() function sends.

The then() method itself always returns a promise so we can chain another then() to it. Let’s focus on what happens here. Imagine there’s on or two handlers in the then method (one for resolve and one for reject). Eventually one of these functions will run.

  • If the handler function returns a value, the promise that then() method returns resolve to that value.
  • If the handler function doesn’t return anything, the promise that then() method returns resolve to undefined.
  • If the handler function throws and error, the promise that then() method returns gets rejected with that error.
  • If the handler function returns a new promise, the then() method returns that new promise.

You can check the code for a sample promise here.

Why Wrap a Promise Definition in a Function

Always remember that a Promise executes as soon as it is created, so if we wanna control when the executor function is executed, we can wrap the promise definition in a function like this:

And then we can consume it like this:

Promises are asynchronous

The beauty of the promises is that they’re asynchronous. In other words they don’t block JavaScript from running. This is how it works: When JavaScript hits a promise, it immediately stores that promise object in the memory. At this time the state of the promise object is pending and its value is undefined and the onfulfillment property is also undefined. If the executor function of that promise does any async operation (like a fetch or XHR request) that operation will be sent to the browser web API (which is outside the JavaScript engine) and the web API will handle that async operation while the JavaScript execution thread continues running the rest of the code. So the async operation (like fetch or XHR) won’t block the main thread.

When we run the then() method on that promise, its callback function will be added as the onfulfillment property to the promise object.

Later on when the web API receives the result of the async operation (like the response of the HTTP request) it’ll push the function passed to then() method (the same function that is in the onfulfillment property) to the microtask queue where it’ll wait to be executed.

Finally when the main thread of execution is done executing (all the synchronous code) the event loop will push that function from the microtask queue to the call stack and then JavaScript will run it. This is when the function we passed to the then() method runs. As a result, the function passed to the then() method always run after all synchronous code is finished running.