These keywords act as a syntactic sugar built on top of Promises, making asynchronous code look and feel like synchronous, therefore easier to produce and maintain.
Important note: before proceeding with this article make sure you understand Promises and how to use them. Refer to this article if you don't.
Async function - it is a function, declared using async
keyword. This function always returns a Promise which will be resolved or rejected and knows how to handle await
inside of it:
const example = async () => 1;
console.log(example()); // Prints "Promise { 1 }"
Note, how the returned value is converted into the Promise. So, how do we actually retrieve the value? (By using then
Promise consumer):
const example = async () => 1;
example()
.then(result => console.log(result)); // Prints "1"
The real benefits of using async
keyword become apparent when you start combining is with await
. In fact, await
is only valid inside of async
functions, you won't be able to use this keyword in a regular function.
The keyword await
makes JavaScript wait until that promise settles(either resolves or rejects) and returns its result:
const example = async () => {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Success");
}, 1000);
});
console.log("Before await");
const result = await promise;
console.log("After await");
return result;
};
/*
Prints:
"Before await"
Promise { <pending> }
"After await"
"Success"
*/
example()
.then(result => console.log(result));
Important note: The function execution “pauses” at the line const result = await promise
and resumes when the promise settles, with result
becoming its result.
Let's consider the exact same example, but without using async/await
keywords:
const example = () => {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Success");
}, 1000);
});
console.log("Before await");
const result = promise;
console.log("After await");
return result;
};
/*
Prints:
"Before await"
"After await"
Promise { <pending> }
"Success"
*/
example()
.then(result => console.log(result));
Note, how the order of logging "Before await" and "After await" has changed.
Have you noticed that something is wrong? Why do we still use then
when we are talking about async/await
?
Actually, there's a good reason for that: await
won’t work in the top-level code, so basically we can't do that (as you remember, await
has to be used in pair with async
):
const result = await example(); // Bad
But it can be wrapped into an anonymous function and executed immediately:
(async () => {
const result = await example(); // Good
})();
If the Promise resolves, await promise
returns the result, if it rejects - an error is thrown:
const example = () => {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject("Error");
}, 1000);
});
const result = promise;
return result;
};
example()
.catch(error => console.log(error)); // Prints "Error"
Basically, both following examples are equivalent:
const exampleReject = async () => {
await Promise.reject("Error");
}
const exampleThrow = async () => {
throw new Error("Error");
}
Thrown error can easily be caught by using try...catch
block:
async function example() {
try {
let response = await fetch("http://notexistingresource");
} catch(error) {
console.log(error);
}
}
example(); // TypeError: Failed to fetch
Async/await is really useful to know about, but there are a couple of downsides to consider.
These keywords make your code look and behave in a synchronous manner.
The await
keyword blocks code execution until the Promise settles, exactly as it would with the synchronous operation.
Important note: it does allow other tasks to continue, just your own code is blocked. That doesn’t cost any CPU resources, because the engine can do other jobs in the meantime: execute other scripts, handle events, etc.
In case of having multiple await
statements, your code execution is slowed down due to waiting for each promise to resolve.
Fortunately, this issue can be resolved by storing each Promise in variable and using Promise.all
method to wait until all promises complete.
Try to avoid this:
const promise1 = new Promise((resolve, reject) => {
resolve(1);
});
const promise2 = new Promise((resolve, reject) => {
resolve(2);
});
const promise3 = new Promise((resolve, reject) => {
resolve(3);
});
(async () => {
await promise1;
await promise2;
await promise3;
})();
Use Promise.all
:
const promise1 = new Promise((resolve, reject) => {
resolve(1);
});
const promise2 = new Promise((resolve, reject) => {
resolve(2);
});
const promise3 = new Promise((resolve, reject) => {
resolve(3);
});
Promise.all([promise1, promise2, promise3])
.then(result => console.log(result));
Async/await is a very powerful tool and it strongly helps you to increase the readability of your code.
async
functiontry...catch
blockawait
keyword. Consider using Promise.all
instead