NodeJS supports async/await out of the box since version 7.6. I believe it has been the single greatest addition to JS since 2017. If you haven’t tried it yet, here are a bunch of reasons with examples why you should adopt it immediately and never look back.
Async/Await 101
For those who have never heard of this topic before, here’s a quick intro
- Async/await is a new way to write asynchronous code. Previous alternatives for asynchronous code are callbacks and promises.
- Async/await is actually just syntax sugar built on top of promises. It cannot be used with plain callbacks or node callbacks.
- Async/await is, like promises, non blocking.
- Async/await makes asynchronous code look and behave a little more like synchronous code. This is where all its power lies.
Syntax
Assuming a function getJSON that returns a promise, and that promise resolves with some JSON object. We just want to call it and log that JSON, then return "done".
This is how you would implement it using promises
And this is how it looks with async/await
There are a few differences here
- Our function has the keyword async before it. The await keyword can only be used inside functions defined with async. Any async function returns a promise implicitly, and the resolve value of the promise will be whatever you return from the function (which is the string "done" in our case).
- The above point implies that we can’t use await in the top level of our code since that is not inside an async function.
// this will not work in top level
// await makeRequest()
// this will work
makeRequest().then((result) => {
// do something
})
3. await getJSON() means that the console.log call will wait until getJSON() promise resolves and print it value.
Why Is It better?
1. Concise and clean
Look at how much code we didn’t write! Even in the contrived example above, it’s clear we saved a decent amount of code. We didn’t have to write .then, create an anonymous function to handle the response, or give a name data to a variable that we don’t need to use. We also avoided nesting our code. These small advantages add up quickly, which will become more obvious in the following code examples.
2. Error handling
Async/await makes it finally possible to handle both synchronous and asynchronous errors with the same construct, good old try/catch. In the example below with promises, the try/catch will not handle if JSON.parse fails because it’s happening inside a promise. We need to call .catch on the promise and duplicate our error handling code, which will (hopefully) be more sophisticated than console.log in your production ready code.
const makeRequest = () => {
try {
getJSON()
.then(result => {
// this parse may fail
const data = JSON.parse(result)
console.log(data)
})
// uncomment this block to handle asynchronous errors
// .catch((err) => {
// console.log(err)
// })
} catch (err) {
console.log(err)
}
}