July 01, 2020 - John Law - 3 mins read
So recently, I need to use Promise
and async
a lot. We're going to see how to program asynchronously.
Traditionally, ancient programmers use callback functions to perform asynchronous tasks. However, with this style, their code will be extremely unreadable. When you gaze long into the abyss. The abyss gazes also into you.
Let's see the following code:
const f1 = () =>
new Promise((resolve, reject) =>
setTimeout(() =>
console.log("f1"), resolve("f1"), 1000));
const f2 = () =>
new Promise((resolve, reject) =>
setTimeout(() =>
console.log("f2"), resolve("f2"), 1250));
const f3 = () =>
new Promise((resolve, reject) =>
setTimeout(() =>
console.log("f3"), resolve("f3"), 1500));
Promise.all([f2(), f3(), f1()]).then((result) => console.trace(result));
We can say that a Promise
represents a possible future value. There may be some reasons for it to be rejected, but we don't know yet! In the above example, we use the method Promise.all()
to perform several asynchronous tasks, in parallel. We immediately see something interesting. The first function called is f1
followed by f3
, and finally f2
, f1
takes ms to finish though. If we add a function to the code:
const f4 = () =>
new Promise((resolve, reject) =>
setTimeout(() =>
console.log("f4"), reject("f4"), 1000));
Promise.all([f2(), f3(), f1(), f4()]).then((result) => console.trace(result));
That wouldn't work. It gives you a list of warning. After catching the rejection, we know that if there's any rejection, Promise.all()
wouldn't work. We have to use Promise.allSettled()
. Therefore, the code:
Promise.allSettled([f2(), f3(), f1(), f4()])
.then((result) => console.trace(result));
will give you
Trace: [
{ status: 'fulfilled', value: 'f2' },
{ status: 'fulfilled', value: 'f3' },
{ status: 'fulfilled', value: 'f1' },
{ status: 'rejected', reason: 'f4' }
]
which is very nice to work with. However, as of July 2020, if you are using Typescript, you will need to add es2020
of lib
in tsconfig.json
to enjoy this feature.
Sometimes, we want to get the first promise begin rejected or resolved. We can use Promise.race()
. This may be useful for requests to multiple identical sources, for example.
async
and await
are introduced in ES2017. It solves the problem of having a lot of callbacks in a chain, improving code readability. It seems with this style, erroneous pieces of code are easier to spot. An async
function is a function that returns a promise. Suppose we need to write something like
const f1 = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
console.log("First");
resolve("First");
}, 1000);
});
f1()
.then((result) => {
setTimeout(() => {
console.log(`Second ${result}`);
}, 1250);
return "Second";
})
.then((result) => {
setTimeout(() => {
console.log(`Third ${result}`);
}, 1500);
});
that gives you
First
Second First
Third Second
The following gives you the same result, but we get rid of all .then()
s. They have similar sizes in bytecode as well.
const f1 = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
console.log("First");
resolve("First");
}, 1000);
});
const f2 = async () => {
const result = await f1();
const secondResult = (() => {
setTimeout(() => {
console.log(`Second ${result}`);
}, 1250);
return "Second";
})(result);
(() => {
setTimeout(() => {
console.log(`Third ${secondResult}`);
}, 1500);
})(secondResult);
}
f2()
All we have to remember is that await
always lives inside an async
function. Otherwise, you will face a syntax error. For Promise.all()
, the usage is similar. async
and await
are better in error handling (its clear syntax and its resultant error stacks) as well.
This is John Law, signing off. You read 334 words.
Copyright © 2017-2022 John Law