Async/await is awesome, but don’t abandon Promises
Let’s create a button that will:
- perform an expensive synchronous operation,
- fire 2 AJAX requests, and
- update the DOM based on the AJAX responses.
Here is the markup.
Here are the functions. Let’s also measure the duration of each operation with the Performance API, which visualizes when and how long each function executes on the Chrome DevTools Performance Timeline. (Thanks to JSONPlaceholder for the dummy endpoints.)
You’re still here? Good, here comes the interesting part: writing the onclick
handler for the button. Since all the cool kids are doing it, let’s use async / await
.
async function handleClick() {
someSyncOperation(); // Expensive sync operation
const postJson = await fetchPost(); // AJAX request #1
const commentsJson = await fetchComments(); // AJAX request #2
appendPostDOM(postJson);
appendCommentsDOM(commentsJson);
}
Here is the Performance Timeline after clicking the button.
Let’s take a closer look.
Makes sense, plenty of articles out there about how async / await
turns asynchronous code into blocking code. FYI, each bar is about 2 seconds when throttling the network to “Slow 3G”.
So a total execution time of 6 seconds.
OK. The fetchPost
and fetchComments
can be executed in parallel, so let’s use the await Promise.all
combo.
async function handleClick() {
someSyncOperation();
const [postJson, commentsJson] = await Promise.all([
fetchPost(),
fetchComments()
]);
appendPostDOM(postJson);
appendCommentsDOM(commentsJson);
}
The total execution time is now 4 seconds since fetchPost
and fetchComments
execute in parallel.
OK. Since someSyncOperation
is not dependent on the AJAX requests, let’s see if moving it to the last line in the function speeds things up.
async function handleClick() {
const [postJson, commentsJson] = await Promise.all([
fetchPost(),
fetchComments()
]);
appendPostDOM(postJson);
appendCommentsDOM(commentsJson);
someSyncOperation();
}
Nope, the total execution time is still 4 seconds.
OK. It’s time to go “full Promise
”.
function handleClick() {
Promise.all([
fetchPost(),
fetchComments()
]).then(([postJson, commentsJson]) => {
appendPostDOM(postJson);
appendCommentsDOM(commentsJson)
});
someSyncOperation();
}
Going “full Promise
” cuts total execution time to 2 seconds.
The reason why this works deserves its own article, but here is an awesome explainer.
Today I learned.
Bonus
For the die-hard async / await
fans out there, I learned (literally on the day of writing this story) that the following snippet actually does the same thing. Credit to this article by Moon.
async function handleClick() {
const postPromise = fetchPost();
const commentsPromise = fetchComments();
someSyncOperation();
const postJson = await postPromise;
const commentsJson = await commentsPromise;
appendPostDOM(postJson);
appendCommentsDOM(commentsJson);
}