JavaScript async/await Cheat Sheet

Iain Maitland
3 min readNov 26, 2020

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

  1. We start by calling await foo() on line 20 which tells JavaScript to wait until foo() fulfills its promise before executing the rest of foobar().
  2. foo() begins executing and the first thing it does is await sleep(4000). on line 8. This tells JavaScript to wait until sleep() fulfills its promise before executing the rest of foo().
  3. After 4 seconds have passed sleep() fulfills and foo() can continue. It executes line 9 and prints foo() done to STDOUT and then returns foo.
  4. Since foo() has fulfilled its promise foobar() can now finish executing line 20 and prints foo — the value returned from foo() to STDOUT.
  5. foo() continues on to line 22 and calls bar(). Notice that it doesn’t use the await keyword. Logically this is a bug — we’ve forgotten to tell JavaScript to wait until bar() fulfills its promise before moving on. So what is printed to STDOUT is Promise { <pending> } because that’s the value of bar() when it’s first created (remember a Promise is a container for some future value that has three states — pending, fulfilled, rejected).
  6. There is nothing left to do in foobar() so it returns and the .then() callback on line 26 is executed printing foobar() done to STDOUT
  7. bar() is still executing in the background and 1s later prints bar() 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.

--

--

Iain Maitland

Senior software engineer at Skyscanner. I mostly write Java and Python back end systems occasionally foraying in to JavaScript. Also a dad and 49ers fan.