JavaScript async/await Cheat Sheet
The async
and await
keywords introduced in JavaScript make it easier to write asynchronous code that looks like synchronous code and is a bit less boilerplate-y. They are really just a small abstraction over promises though so if you don’t understand promises read this first.
An async function that returns a value
Functions prefixed with the async
keyword implicitly return a promise. When the function returns a value the promise is fulfilled.
The async implementation of foo
above is equivalent to the below implementation that explicitly returns a Promise
Using await keyword to block execution until a promise fulfills
Use the await
keyword to stop a function from continuing until an async
function completes.
Keep in mind that await
can only be used inside an async
function — hence why we use .then().catch()
when calling bar()
but inside bar()
we can await
the result of foo()
.
An async function that throws an Error
Just like how inside a promise throwing an exception like throw new Error('bang')
is equivalent to reject(new Error('bang'))
an async function that throws an exception is equivalent to a promise that rejects.
Again, the async implementation of foo
above is equivalent to this version using Promise
.
Handling Errors with try…catch
Inside an async
function that calls another async
function a try...catch
can be used to handle cases where async
functions reject.
In the example below bar()
wraps a call to await foo()
with try...catch
. Since foo()
throws an Error
i.e., the promise rejects, the result of await foo()
on line 7 is an Error
so execution will fall in to the catch()
block.
Gotchas
await
doesn’t block all the way up the call stack
await
only blocks inside the function it is used and not all the way up the call stack. This is easier to explain with an example. Let’s also introduce an async function sleep
that will take a few seconds before fulfilling so we can simulate some latency. We’ll do this by wrapping setTimeout()
in a Promise
.
You might not expect bar() done
to be printed after foobar() done
. Let’s walk through what’s happening here
- We start by calling
await foo()
on line 20 which tells JavaScript to wait untilfoo()
fulfills its promise before executing the rest offoobar()
. foo()
begins executing and the first thing it does isawait sleep(4000)
. on line 8. This tells JavaScript to wait untilsleep()
fulfills its promise before executing the rest offoo()
.- After 4 seconds have passed
sleep()
fulfills andfoo()
can continue. It executes line 9 and printsfoo() done
to STDOUT and then returnsfoo
. - Since
foo()
has fulfilled its promisefoobar()
can now finish executing line 20 and printsfoo
— the value returned fromfoo()
to STDOUT. foo()
continues on to line 22 and callsbar()
. Notice that it doesn’t use theawait
keyword. Logically this is a bug — we’ve forgotten to tell JavaScript to wait untilbar()
fulfills its promise before moving on. So what is printed to STDOUT isPromise { <pending> }
because that’s the value ofbar()
when it’s first created (remember aPromise
is a container for some future value that has three states — pending, fulfilled, rejected).- There is nothing left to do in
foobar()
so it returns and the.then()
callback on line 26 is executed printingfoobar() done
to STDOUT bar()
is still executing in the background and 1s later printsbar() done
to STDOUT
UnhandledPromiseRejectionWarning
This is usually a hint that you have called an async function and failed to handle its error case i.e., its promise rejecting. It’s a surprisingly easy mistake to make. Let’s use our try...catch
example with one small change — don’t await the call to foo()
on line 7
What’s happening here is without using await
to block until foo()
completes JavaScript evaluates line 7 to be a pending Promise
. Since that’s not an Error
and there is no other code to evaluate bar()
completes. But foo()
is still processing in the background so when it does eventually throw an Error
there is nothing to handle it hence the UnhandledPromiseRejectionWarning
.
If you’re surprised that the callback in bar().catch(e => console.error(e));
didn’t catch the Error
thrown by foo()
remember that bar()
technically fulfilled its promise and so bar().catch()
was never called. Instead the callback inside bar().then(() => console.log('bar() done')
was called . That’s why we see bar done()
printed to STDOUT.