Supported in Node.js since v7.6, Async/Await is hands down one of the best features to come out of an ECMAScript release. And because it changes a lot the way we write asynchronous code, some new patterns - and confusions - emerge along with it.
The next sections introduce a few concepts and “gotchas” I’ve come across after a while writing code using Async/Await.
Awaiting promises can’t be done top level.
This means the
await operator cannot be used in the top level of your code. It always have to be wrapped in an
async function (a function with the
async keyword on the beginning).
The example below shows how to do it using an IIFE:
In short, allowing top-level
await could cause unexpected behaviours while loading modules on your code (the thing when you do
import x from 'x').
The debate is not new, folks have been talking about top-level
await even before Async/Await got accepted into Stage 3 of the ECMAScript spec process. There’s more on these links if you want to dig deeper on the subject: , , 
And if you are feeling more adventurous, you can try this.
Every function that begins with the
asynckeyword is an
AsyncFunctionalways returns a
That is very fundamental and very important knowing. Any value that you return in an
async function will be thenable, you don’t have to worry about using the the Promise constructor or calling
If an error is thrown inside of an
async function, the Promise will be rejected as well. See more about
async functions on the MDN docs.
asyncfunctions always wrap your results in
Promise.resolve, it is redundant to return a value with the
Take a look at the function below:
Internally, what you’re doing is wrapping
Promise.resolve twice: one from using an
async function and the other from the
return await doesn’t actually do anything more than adding an extra time before the overarching Promise resolves or rejects.
awaitinside of a
Array.mapcallback is tricky and probably won’t work as you expect.
Consider the following code:
Array.map - and also
Array.forEach - are not expecting an
async function as its callback parameter and will just fire your function as if it were synchronous.
This results in all calls to
get being fired and executed in parallel. The
console.log will also run immediately and print 0 since it is much faster than any of the HTTP requests.
Besides that, wouldn’t it be odd if a child function (the callback on the example) was able to control the flow of its parent?
To run tasks in sequence the right tool is the
for..of statement. We could rewrite the example above like this:
Looks much cleaner and the requests will all execute in sequence.
console.log is going to be executed only after the last iteration of the
for..of and will print the number 3.
Another common use case is when you want to execute an array of asynchronous tasks in parallel and only run code after the last task finishes. For that we would use
Promise.all, a function that turns an array of promises into a single promise that we can
We can then rewrite our code like this:
Now because the requests are running in parallel, our code is much more efficient. The
console.log will now print the correct result since it only runs after the last request finishes.
As a side note, be careful when writing loops with
for..of is so easy to write, it can be tempting to run every array of tasks with it. Altough it is not technically wrong, you may be missing an opportunity to run the tasks in parallel and improve performance. There’s even an ESLint rule to disallow using
await inside of loops.
Unhnandled errors in Async/Await are swallowed “silently”.
This means you’ll want to either use a
try/catch statement around your
await declarations or a
.catch at then end of it. See the example:
Like in Promises, unhandled errors propagate (bubble up).
We don’t have to wrap our code in a
try/catch every time you use the
await operator. In a scenario where a parent function calls others
async functions, we can leverage the fact that errors bubble up to handle them in a single place.
In the code above, any rejected promises returned by the functions within
findNewJobs will be handled inside of the “catch-all” block.