A Beginner's Guide to Async/Await
Joanna
Posted on December 15, 2019
Async/await was rolled out with ECMAScript 2017, and enables our asynchronous code to flow even more like synchronous code. It's basically syntactic sugar for Promises, so like Promises, it helps us avoid writing deep chains of nested callbacks, and is arguably even more readable than regular Promises.
Syntax
To declare an async function, put async
at the beginning of your function declaration.
Like so:
async function greeting(word) {
return word;
}
Using the async
keyword lets the interpreter know that the function should evaluate to a Promise instead of directly returning a value.
So the function above is effectively the same as:
function greeting(word) {
return Promise.resolve(word);
}
In the second function, we are explicitly stating that this function returns a Promise. In the first one, the async
keyword is doing that implicitly for us.
Let's resolve it
Technically, we can call an async function just like a normal Promise, using a .then()
statement to make sure it evaluates to a value, and not just a Promise object.
async function greeting(word) {
return word;
}
greeting('Hi'); // Promise { ‘Hi’ } <-- evaluates to a Promise object
greeting('Hey')
.then(word => console.log(word)); // ‘Hey’ <-- evaluates to a fulfilled Promise
If we call greeting
by itself, then it’s going to evaluate to a pending Promise object, so we could use .then()
to make sure it the Promise is fulfilled.
But (a)wait!
What makes an async function so exciting is the possibility of using the await
keyword. When we put await
in front of an expression in our async function, we’re telling the interpreter, hey, this expression returns a Promise, so wait-- pause code execution here until the function that follows await
is finished running, and then resolve that Promise!
Let's try it out
async function makeCall() {
try {
const response = await axios.get('https://somewebsite.com');
console.log(response.data);
} catch(error) {
console.log(error);
}
};
So we’ve got an async function makeCall()
. Our axios GET request is being called with the await
keyword, which means that nothing happens with the rest of makeCall()
until the response is received from the axios request.
The axios library is also built on Promises-- axios requests return Promises. But if a function does not inherently return a Promise, await
turns it into one that does.
The await
keyword basically injects the resolve
of a Promise that would otherwise need to be finished/fulfilled with a .then()
.
Here, we’re storing that resolve in a variable because we want to do something with it-- namely, grab the data from that response object and then console log it.
(We handle our errors by placing the body of our main call in a try
block, and follow that up with a catch
block.)
Pretty cool! And a bit easier to read than this:
function makeCall() {
return axios.get('https://somewebsite.com')
.then((response) => {
console.log(response)
}).catch((error) => {
console.log(error)
});
}
Avoid chaining
With normal Promises, when we have multiple Promises that need to be resolved, our code could look something like this:
function makeThreeCalls() {
return axios.get('https://someurl.com')
.then(() => {
axios.get('https://anotherurl.com')
}).then(() => {
axios.get('https://yetanotherurl.com')
}).catch((error) => {
console.log(error);
})
}
This is definitely better than callback hell. But we're still left with this chain of .then()
s, and each of those .then()
s have their own arrow function.
Using async/await
makes our code even more readable, especially when multiple calls are involved.
async function makeThreeCalls() {
const firstCall = await axios.get('https://someurl.com');
const secondCall = await axios.get('https://anotherurl.com');
const thirdCall = await axios.get('https://yetanotherurl.com');
console.log(`${firstCall}, ${secondCall}, ${thirdCall}`)
}
This is an async function with three await
statements in one block of code. It just reads a little more like synchronous code, doesn't it?
We've got three axios requests that are preceded by the await
keyword, which means they’re going to be resolved Promises. Our interpreter waits for one request to be done before it invokes the next.
Promise.all()
We can speed this process up the same way we would with normal Promises. Instead of running the functions one by one, waiting for one function to resolve before we call the next, we can run them in parallel. We do this by calling them with Promise.all()
.
async function makeThreeCalls() {
const [ firstCall, secondCall, thirdCall ] = Promise.all([
await axios.get('https://someurl.com'),
await axios.get('https://anotherurl.com'),
await axios.get('https://yetanotherurl.com')
]);
console.log(`${firstCall}, ${secondCall}, ${thirdCall}`)
}
This saves a lot of time.
~~~~
I hope this was a helpful introduction to async/await
, which is just syntactic sugar on top of Promises. It can make your code even more intuitive and easier to read. It allows us to write asynchronous code that reads more like synchronous code, and helps us avoid the .then()
chains that are still necessary with normal Promises.
Posted on December 15, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 10, 2023