JavaScript Promises: Unlocking the Power of Asynchronous Programming
Explore JavaScript promises: Their mechanics and significance
You are already familiar with the word Promise
. We all make several promises and pledges in life to our loved ones, friends, and family, which we refer to as promises in real-time.
Promises and pie-crust are made to be broken.
- Jonathan Swift
What is a promise in tech terms?
A promise is a JavaScript object that represents the completion of an asynchronous operation and the resulting value. In basic terms, promise handles time-consuming tasks like fetching data from an API, reading files, or performing complex calculations.
JavaScript promises have three states: pending, resolved, and rejected.
When you execute a promise, it first enters the pending state while waiting for fulfillment. Once settled, the promise can have two outcomes: resolved
, indicating successful fulfillment with data provided to the .then()
function, or rejected
, where the error is sent to the .catch()
function. If a function is called with a promise, it will keep running until the promise is settled and the desired data is returned to the calling function.
For example:
Different approaches to promises
- Promise.all
- Promise.any
- Promise.race
- Promise.allSettled
- Sequences of promises
Let’s deep-dive into the promises.
1. Promise.all
Promise.all
is the integrated function that takes an array of promises and returns a combined promise. When resolved, it provides an array of all resolved values passed as arguments. If any promise is rejected, the combined promise is also rejected.
Syntax:
Promise.all(iterable)
Note: Here, iterable is an array of promises.
In this example, we have three promises. When promise1 resolves, it returns “foo.” Later, promise2 resolves, providing the string “bar,” while the third promise gets rejected, resulting in an error object with the value “baz.”
Using Promise.all(),
we run both promises simultaneously, waiting for their completion before continuing. The results are returned as an array in the original order.
In the first call to Promise.all()
, both promises are successfully resolved, leading to the execution of the .then()
block and producing the values [‘foo’, ‘bar’].
However, in the second Promise.all()
call, one promise (promise3) is rejected, triggering the .catch()
block and outputting the error ‘baz’.
In general, Promise.all()
is a good option when we want to ensure that multiple promises are fulfilled before progressing.
2. Promise.any
The Promise.any()
function in the JavaScript promise API accepts an array of promises as input and returns a new promise.
It resolves with the value of the first resolved promise or fails with an AggregateError
instance if all promises fail.
Syntax:
Promise.any(iterable)
Note: Here, iterable is an array of promises.
In this scenario, we have four promises: promise1 and promise2 resolve as fulfilled after a 1-second delay, while promise3 and promise4 are rejected after a 2-second delay.
Case 1: After the specified time, Promise.any()
fulfills both promises, with promise1 resolving first and returning ‘Promise 1 resolved’.
Case 2: One of the promises resolves successfully, resulting in ‘Promise 2 resolved’ when promise2 is fulfilled.
Case 3: If none of the promises are fulfilled and all are rejected, Promise.any()
returns an AggregateError
with an array of all the rejected reasons. The system reports an issue with the messages ‘Promise 3 rejected’ and ‘Promise 4 rejected’.
In most cases, Promise.any()
continues without waiting for subsequent promises to be fulfilled, returning immediately.
3. Promise.race
Promise.race()
receives an array of promises and promptly resolves or rejects based on the first resolved or rejected promise.
The name Promise.race()
implies a competition among promises to determine the winner, regardless of resolution status. It returns the first settled promise.
Syntax:
Promise.race(iterable)
Note: Here, iterable is an array of promises.
In this scenario, we made four promises: promise1 and promise2 are resolved within the mentioned time. However, promise3 and promise4 are both declined and rejected.
Case 1: promise1 resolves in 1 second, faster than promise2 (2 seconds). Promise.race()
completes with ‘Promise 1 resolved’.
Case 2: promise2 settles in 2 seconds, while promise3 takes 3 seconds. Thus, promise2 is resolved first, executing .then()
and returning ‘Promise 2 resolved’.
Case 3: promise3 settles in 4 seconds, promise4 in 3 seconds. Promise.race()
rejects promise4 as it settles first, invoking .catch()
and returning ‘Promise 4 rejected’.
Overall, Promise.race()
handles multiple promises simultaneously and returns the result of the first settled promise. It’s useful for obtaining the first outcome of asynchronous activities.
4. Promise.allsettled
The Promise.allsettled()
function in the JavaScript promise API accepts an array of promises as input and returns a new promise. This new promise settles when all of the promises in the iterable have been settled, i.e., whether fulfilled or rejected.
After resolving each of the input promises, it returns a new promise with an array of objects. These objects have two attributes: status and either value or reason
. Fulfilled promises have value, while rejected promises have a reason.
Syntax:
Promise.allsettled(iterable)
Note: Here, iterable is an array of promises.
Case 1: Two promises, promise1 and promise2, settle after 2 seconds. They return ‘Promise 1 resolved’ and ‘Promise 2 resolved’, respectively, both fulfilled.
Case 2: Two promises, promise2 and promise3, settle after 3 seconds. They return ‘Promise 2 resolved’ (fulfilled) and ‘Promise 3 rejected’ (rejected).
Case 3: Two promises, promise3 and promise4, settle after 4 seconds. They return ‘Promise 3 rejected’ and ‘Promise 4 rejected’, with rejected status.
In general, Promise.allSettled()
function efficiently handles multiple promises concurrently, managing all outcomes (fulfilled or rejected).
5. Sequence of Promises
In a Sequence of Promises,
one promise executes after the previous one is resolved, known as Nested Promises
or Promise Pyramids
. This hierarchical structure allows each promise to depend on its parent promise’s resolution, providing flexibility with outer scope variables.
Case 1: We have a chain of three promises here that execute sequentially, resulting in successful execution and producing success logs. The catch block won’t be invoked in this scenario.
Case 2: In this chain of three promises, one promise may be rejected. Despite that, the promises still execute sequentially, generating success logs until a rejection occurs. In this case, the catch block will be executed.
Note: Sequence of Promises
resolves the Callback hell problem.
Sometimes, it can cause problems with code readability and the straightforward handling of errors. To avoid this, we can use the modern asynchronous JavaScript technique of async/await.
Let’s understand async/await with an example:
The example in Case 1 has been updated using async/await syntax, which improves the readability and structure of the code.
Parting Words
Promises offer chaining, error handling, parallel processing, and concise syntax, simplifying asynchronous tasks like ordering and tracking food delivery online. It informs you about food readiness and any issues.
Asynchronous JavaScript promises remain vital in software development, enabling highly efficient and user-friendly applications.
For more updates on the latest tools and technologies, follow the Simform Engineering blog.