Promise
is one of the best APIs for asynchronous operations in JavaScript. As a JavaScript developer, you need to be proficient in Promise
. This article will summarise this knowledge, starting with a review of how JavaScript has handled asynchronous operations in the past? Then we’ll go into detail about the Promises object and its associated methods.
Let’s start with a look at JavaScript asynchronous concepts.
Asynchronous
What is asynchronous? A function is asynchronous if the expected result is not yet available to the caller when the function returns, but will be available in the future by some means (e.g. a callback function).
Asynchronous callbacks
Asynchronous callbacks, commonly known as callbacks
, were a common way of handling asynchronous operations in JavaScript in the past, such as AJAX, where an HTTP request was initiated and the exact time when the server returned the response data depended on the client’s environment and there was a lot of uncertainty, when a callback function was used to trigger the callback function when the response data was returned.
This approach is common when there are multiple requests and subsequent requests need to wait for the response data of the previous request when the callback hell occurs.
|
|
The presence of callback hell will cause many problems for project development.
- It will lead to confusing logic, high coupling, one change will lead to all changes, and when nested, bugs are difficult to find.
- You can’t use
try.... .catch
to catch exceptions. - Can’t use
return
to return real data
To avoid callback hell, the Promise
object was created.
How Promise works
A promise
object is an object that can be returned synchronously from an asynchronous function and will be in one of 3 possible states.
fulfilled
:onFulfilled()
will be called, i.e. the operation is complete (e.g.resolve()
is called)rejected
:onRejected()
will be called, i.e. the operation has failed (e.g.reject()
is called)pending
: initial state, neither honoured nor rejected
If a promise
does not hang (it has been resolved or rejected), it will be resolved. Sometimes resolved and resolved are used to indicate the same thing: not pending
.
Once a promise
has been determined, it cannot be redetermined, and calling resolve()
or reject()
again will have no effect. A determined promise
has immutability.
For monitoring the state of a promise
you can use a promise chain, i.e. in the fulfilled
honoured state you can use the then
method to get the honoured result, and in the rejected
rejected state you can use the catch
method to get the reason for the rejection.
It looks a little more elegant than the callback
approach, and for needs that require multiple HTTP requests to be initiated for complete rendering, the code looks like this.
|
|
Does the above code feel like déjà vu, originally intended to solve callback hell, but there seems to be a gap between the ideal and the reality.
So ES2021 adds new features to the Promise object, including: Promise.any()
, Promise.all()
, Promise.allSettled()
, and Promise.race()
.
Promise.any()
Promise.any(promises)
runs promise
in parallel and resolves to the value of the first successfully resolved promise
in the list of promises
. Note that the Promise.any()
method is still experimental and is not fully supported by all browsers.
Here is a look at how Promise.any()
works.
1. How it works
Promise.any()
can be used to perform independent asynchronous operations in a parallel and competitive manner to get the value of any first completed promise
.
The function accepts an promise
array (usually an iterable object) as an argument, as follows.
|
|
When the first promise
in the input promises
is executed, anyPromise
is immediately resolved to the value of that promise
.
The value of the first promise
can be extracted using the then
method.
The async/await
syntax can also be used.
The promise
returned by romise.any()
is executed together with any first promise
that is executed. Even if some promise
s are rejected
, these rejections
will be ignored.
However, if all promises
in the input array are rejected, or if the input array is empty, then Promise.any()
will rejected
contain the set of rejection
error reasons for the execution of the input promises
.
2. Usage Guidelines
Before we dive into Promise.any()
, let’s define 2 simple functions.
The function
resolveTimeout(value, delay)
will return apromise
that hasresolved
afterdelay
time has elapsed.
The function
rejectTimeout(reason, delay)
returns apromise
that has areject
after thedelay
time has elapsed.
Next, try Promise.any()
using the 2 helper functions defined above.
2.1 Completing all promises
Try running the first parsed list as follows.
|
|
promise .any([...])
returns a promise
that parses the array fruits
in 1 second
, since the promise
that parses the fruits finishes first.
The second is a promise
that resolves to the array vegetables
in 2 seconds
, and its value is ignored.
2.2 A promise
is rejected
The first promise
above is rejected
with an exception, as follows.
|
|
In the above code, the first promise
is rejected
after 1 second
and it is easy to see from the result of the execution that Promise.any()
skips the first promise
that is rejected
and waits for the second promise
that finishes 2 seconds
later.
2.3 All promises
are rejected
Here’s what happens when all promises
are rejected
, with the following code.
|
|
Summary
Promise.any()
can be used to execute independent asynchronous operations in parallel in a competitive manner to obtain the value of any first promise
that completes successfully. If all input promises
to Promise.any()
have been rejected
, then the promise
returned by the helper function will also be rejected as an aggregate error, which contains the reason for the rejection of the input promise
in a special property AggregateError
: aggregateError.errors
.
Promise.all()
method Promise.all(promises)
, can process multiple promises
at once in parallel and return only one promise
instance, the result of the resolve
callback for all the promises
entered is an array.
Here’s how Promise.all()
works.
1. How it works
Promise.all()
is a built-in helper function that takes a set of promises
(or an iterable object) and returns a promise
: Promise.all()
is a built-in helper function that takes a set of promises
(or an iterable object) and returns a promise
.
|
|
The value of the first promise
can be extracted using the then
method.
The async/await
syntax can also be used.
The way in which the promise
returned by Promise.all()
was parsed or rejected.
If allPromise
are all successfully parsed, then allPromise
will use as its result an array containing the values of each promise
after it has been executed. The order of the promises
in the array is important - the implemented values will be obtained in this order.
But if at least one promise
is rejected
, then allPromise
is immediately rejected
for the same reason (without waiting for the execution of other promises
).
If all promise
s are rejected
, wait for all promise
s to finish, but only return the reject
reason for the first promise
that was rejected
.
2. Usage Guidelines
Before we dive into Promise.all()
, let’s define 2 simple functions.
The function
resolveTimeout(value, delay)
will return apromise
that hasresolved
afterdelay
time has elapsed.
The function
rejectTimeout(reason, delay)
returns apromise
that has areject
after thedelay
time has elapsed.
Next, try Promise.all()
using the 2 helper functions defined above.
2.1 Completing all promises
An promise
array allPromise
is defined below, and all promises
are capable of successful resolve
values, as follows.
|
|
The result of the above execution shows that the resolve
array of promise
returned by Promise.all()
is composed in the order of allPromise
before it is executed.
The order of the
promise
arrays directly affects the order of the result, regardless of the order in which the execution ofpromise
completes.
2.2 A promise
is rejected
The first promise
of the array allPromise
above is rejected
with an exception, as follows.
|
|
However, after 5 seconds
, the first promise
is rejected
due to an exception, causing allPromise
to be rejected
as well, and returning the same error message as the first promise
: Error: fruits is empty
, even though the value of the second promise
after 1 second
is completed, the value of the second promise
is not accepted.
Next, all the promises
in the array allPromise
are thrown with an exception and rejected
, and the order of rejected
is adjusted by the timer as follows.
|
|
After 5 seconds
, the execution completes and the result is Error: vegetables is empty
, it is easy to see that allPromise
was rejected
because the promise
that was rejected
first.
This behaviour of > Promise.all()
is known as fast failure, and if at least one promise
in the promise
array is rejected
, then the returned promise
is also rejected. If all of the promise
arrays are rejected
, then the returned promise
is rejected because of the one that was rejected
first.
Summary
Promise.all()
is the best way to perform asynchronous operations in parallel and get all the resolve
values, ideal for situations where you need to get the results of the asynchronous operations simultaneously to perform the next operation.
Promise.allSettled()
method Promise.allSettled(promises)
, returns a promise
after all the given promises
have been fulfilled
or rejected
, with an array of objects, each representing the corresponding promise
result.
Here’s how Promise.allSettled()
works.
1. How it works
Promise.allSettled()
can be used to execute independent asynchronous operations in parallel and collect the results of those asynchronous operations.
The function accepts an promise
array (or usually an iterable one) as an argument, as follows.
|
|
When all input promises
have been fulfilled or rejected, statusesPromise
is resolved to an array with its status.
{ status: 'fulfilled', value:value }
: if the correspondingpromise
has been fulfilled{ status: 'rejected', reason: reason }
: if the correspondingpromise
was rejected
The state of all promises
can be extracted using the then
method.
The async/await
syntax can also be used.
The promise returned by Promise.allSettled()
is always realized with a range of states, regardless of whether some (or all) of the input promises are rejected.
The big difference between
Promise.allSettled()
andPromise.all()
:Promise.allSettled()
will never berejected
.
2. Usage Guidelines
Before we dive into the use of Promise.allSettled()
, let’s define 2 simple functions.
Next, try Promise.allSettled()
using the 2 helper functions defined above.
2.1 Completing all promises
An promise
array statusesPromise
is defined below, and all promises
are capable of successful resolve
values, as follows.
|
|
From the results of the above execution Promise.allSettled()
returns an array of resolve
states of a promise
in the order in which the statusesPromise
were composed before it was executed.
2.2 A promise
is rejected
The first promise
above is rejected
with an exception, with the following code.
|
|
Even if the second promise
in the input array is rejected
, statusesPromise
can still successfully parse the state array.
2.3 All promises
are rejected
All of the above promises
with exceptions are rejected
, with the following code.
|
|
Summary
Promise.allSettled()
is a good choice when parallel and independent asynchronous operations need to be performed and all results collected, even if some asynchronous operations may fail.
Promise.race()
method Promise.race(promises)
, as the name implies, means race, Promise.race([p1, p2, p3]
fetches the result of whichever of the promise
arrays completes faster, regardless of whether the result itself is a successful fulfillment or a failed rejection state, and outputs only the fastest promise
.
Here’s how Promise.race()
works.
1. How it works
Promise.race()
returns a promise
that will be fulfilled or rejected once one of the promises
in the iterator has been fulfilled or rejected.
The function accepts an promise
array (or usually an iterable) as an argument, as follows.
|
|
When one of all input promises
is quickly fulfilled or rejected, racePromise
resolves the fast-completing promise
result (fulfilled or rejected) as follows.
The result of racePromise
can be extracted using the then
method.
The async/await
syntax can also be used.
Promise.race()
Returns a promise with the same information as the first promise to complete.
Difference between
Promise.race()
andPromise.any()
:Promise.race()
looks for the first fulfilled or rejected promise in the list of promises;Promise.any()
looks for the first fulfilled promise from the list of promises.
2. Usage Guidelines
Before we dive into the use of ``Promise.race()`, let’s define 2 simple functions as well.
Next, try Promise.race()
using the 2 helper functions defined above.
2.1 Completing all promises
An promise
array racePromise
is defined below, and all promises
are capable of successful resolve
values as follows
|
|
From the above execution Promise.race()
returns the resolve
result of the fastest performing promise
.
2.2 A promise
is rejected
The first promise
above is rejected
with an exception, as follows.
|
|
From the above result, the first promise
to complete is rejected
, so the promise
returned by fastPromise
is also rejected
.
The following extends the promise time for rejected
to 5 seconds, as follows.
|
|
As you can see from the above results, the fastest completed promise
fulfilled resolve
, so the promise
returned by fastPromise
also fulfilled resolve
.
2.3 All promises
are rejected
All the promises
above are rejected
with an exception, as follows.
|
|
From the results, although both promises were rejected, the promise
returned by fastPromise
was the fastest to be rejected.
3. Usage scenarios
3.1. Performance testing
In projects with asynchronous operations, you can use Promises
to test the performance of network or database requests by using Promise.race()
to test the responsiveness of the two different methods.
3.2 Optimal choices
For example fetching the same type of data with multiple requesting servers, sending requests to multiple servers at the same time and rendering their data as soon as one of them completes its work, to achieve the effect of choosing the best route. This is done by using Promise.race()
to execute promise
at the same time and finish as soon as the first one succeeds.
Summary
Promise.race()
executes a callback function for the first resolved and rejected promise
, while Promise.any()
executes a callback function for the first fulfilled promise
and rejects a special property AggregateError
if the unfulfilled promise
is rejected.