JavaScript Promises can be a tricky topic to wrap your head around, especially if you're just starting out with asynchronous programming. Even experienced developers can sometimes struggle with understanding how Promises work and when to use them. With so many online articles about Promises, it can be overwhelming to find a clear and concise explanation.
In this blog post, I'll be breaking down the concept of Promises into the most relevant sub-topics to help make it easier for anyone learning about this critical topic.
Prerequisites
There are only two things I believe are essential to know to follow along in this article. I have listed them below and added some explanations and references.
Asynchronous Programming: According to MDN, Asynchronous Programming is a technique that enables your program to start a potentially long-running task and still be responsive to other events while that async task keeps running, rather than having to wait until that task has finished.
Synchronous Programming: Exactly as it sounds, the code is executed on a line-by-line basis. Each line is run before the next line of code.
What is a Promise?
MDN defines a promise as a proxy for a value not necessarily known when the promise is created. It allows async operations to return a value even before the async operation is completed, such that you can attach functions that will run depending on if the promise got fulfilled or rejected. A Promise is a JavaScript Object, it can be in either of the following three states;
Pending: This is the initial state. At this point, the async operation is still running.
Fulfilled: In this state, the async operation ran and was completed successfully.
Rejected: In this state, the async operation failed and is being rejected.
Creating a Promise
Most of the time, you’ll be handling the rejection/fulfillment of a promise rather than creating it, but pay attention as there are instances where you may have to create a promise too. Creating a promise entails declaring an instance of the Promise class in the manner below;
const promise = new Promise();
It’s important to note that the Promise constructor function accepts a function as an argument. This function has two parameters;
the
resolve
method which is called to change the status of the promise tofulfilled
the
reject
method which is called to change the status of the promise torejected
.
Such that;
const promise = new Promise((resolve, reject) => {
});
How To reject/fulfill a promise
To reject a promise, you’ll need to call the reject function, which takes an argument that will be the parameter of the callback function that will handle the promise rejection. In the same vain, the resolve function is called to resolve a promise passing it a value that will be the parameter in the callback function that handles a resolved promise.
const promise = new Promise((resolve, reject) => {
// performs asynchronous operation
// if promise fails - to set the status of the promise to rejected
reject("This promise failed!");
//if promise succeeds - to set the status of the promise to fulfilled
resolve("Promise has been fulfilled");
});
Calling the reject
method sets the status of the Promise to rejected
, and calling the resolve
method sets the status of the promise to fulfilled
. You would typically call either of these functions after executing an asynchronous operation, depending on whether it failed or succeeded.
Handling a promise rejection or fulfillment
The promise instance gives access to two methods;
The
then
method takes a callback function that handles the Promise fulfillment. The then method also tasks an optional second argument called therejectionCallbackFunction
, which can be used instead of a catch method to handle a rejected promise. However, it is wildly recommended to use thecatch
method instead because it handles all errors that may occur and not just the Promise rejection.The
catch
method takes a callback function that handles the Promise rejection and any other error that may have occurred in thefulfillmentCallbackFunction
.
Something important to note here is that the callback functions have whatever value is passed as an argument to either the reject
or resolve
function, depending on the case, injected as a parameter. Such that we have;
// In case of a fulfilled Promise
promise.then(fulfillmentCallbackFunction)
// In the case of a rejected Promise
promise.catch(rejectionCallbackFunction)
Where;
const fulfillmentCallbackFunction =(relsoveMethodArgument)=>{
console.log(relsoveMethodArgument);
// logs "Promise has been fulfilled" to the console.
}
And;
const rejectionCallbackFunction =(rejectMethodArgument)=>{
console.log(rejectMethodArgument);
// logs "This promise failed!" to the console.
}
How the JS runtime handles promises
It would be incomplete to discuss promises in JavaScript without discussing how the JavaScript engine handles them. Because of its single-threaded nature, the JS runtime uses the event loop mechanism to handle a promise. What is that? you may ask. The event loop is a constantly running process that monitors both the callback queue/Micro task queue and the call stack, checking if the call stack is empty to push pending queued functions to the call stack for execution. By this, the JavaScript engine is able to go on executing other lines of code while the promise is pending.
Summary
A promise is a proxy for a value not necessarily known when the promise is created.
You can create a Promise by declaring an instance of the Promise class and passing a callback function that has two parameters;
resolve
andreject
.Handling promise rejection or fulfillment can be done by passing respective callback functions to either the
then
orcatch
method depending on the status.The JavaScript runtime handles promises with the event loop mechanism.
References
Promises, MDN
Introducing Asynchronous JavaScript, MDN
Synchronous and Asynchronous JavaScript, GeeksforGeeks
Event Loop, JavaScriptTutorials