Async/Await vs Promises — A Guide and Cheat Sheet

Kait Hoehne
Level Up Coding
Published in
5 min readNov 28, 2018

--

I’m a couple of months into my first full-time job as a software engineer ( A moment of celebration: 🎉 🤓) and in the wilds of the real world, one of the first things I dealt with was async/await.

I felt I had a solid understanding of asynchronous workflow in JavaScript and had worked with promises quite a bit, but I ran into a few confusing moments switching to async/await, so I put together a guide and cheat-sheet on the differences between the two.

A note on syntactic sugar: Before we dive in, it’s important to mention that async/await is just a wrapper to restyle code, making promises easier to read and use in certain circumstances. At its core, async/await still uses promises. If you’re unsure of how promises work or need a better understanding of asynchronism in JavaScript, check out this overview.

Example time. Let’s say we want to use a promise to print a string. We’ll resolve the promise by logging a message with our string, and we’ll reject the promise if we get anything but a string. It’s silly, but it’ll illustrate what we’re doing really clearly:

const returnsAPromise = function(string) => {
return new Promise((resolve, reject) => {
if (typeof string === 'string') {
resolve(`${string} is a resolved promise now`);
} else {
reject('Not a string!');
}
});
};

If you want an example that’s slightly more practical, check out this small gist on an asynchronous call to a database:

Scope

The biggest difference I noticed between promises and async/await is the scope of the asynchronism.

Promises

If we use our promise-returning function and keep the results in a standard promise chain, it’ll look something like the function below. The asynchronism here is all contained in the promise chain. myFunction operates like any callback would, running synchronously. Our last log will print “I’m over here running synchronously” before returnsAPromise resolves.

Async/Await

Now, using async/await, our code might look like the function below. In our async/await version, we have to make our entire wrapper function asynchronous. This has a couple of effects— now, myAsyncFunction returns a promise, and any synchronous code we want to run has to be pulled out of the function entirely.

Check out the Gist for the code snippet

Logic Flow

Because the scope changes slightly between promise chains and async/await, we can see that the logic of our code will change as well. A really good way to illustrate this shift in approach is by looking at how both methods resolve multiple promises. Let’s use returnsAPromise again.

Promises

To resolve multiple promises using a promise chain, we can use Promise.all():

const myFirstString = "Kait's first string";
const mySecondString = "Kait's second string";
// Operation A & Operation B can run in parallel
Promise.all([returnsAPromise(str1), returnsAPromise(str2)])
.then(res => {
// Operation C needs info from Operations A & B
console.log(`Promise.all() gives us an array: ${res}`)
})

Async/Await

To resolve multiple promises using async/await, we can simply await both promises, store the results in variables, and use them later in the code block.

const multipleAwaits = async (str1, str2) => {
// Operation A runs
const promiseResult1 = await returnsAPromise(str1);
// Then Operation B runs
const promiseResult2 = await returnsAPromise(str2);

// Then, Operation C runs
console.log(`With multiple awaits, we can use the variables
directly: ${promiseResult1} AND ${promiseResult2}`);
}
multipleAwaits(myFirstString, mySecondString);

A note on Promise.all(): Using multiple await statements is slightly different than our promise chain method. Here, Operation B will await the completion of Operation A. If you truly want your promises to run at the same time, you can still use Promise.all() with your async function and use array destructuring to grab the results. It would look like this:

const multipleAwaits = async (str1, str2) => {
// Operation A & Operation B can run in parallel
const [promiseResult1, promiseResult2] = await
Promise.all([returnsAPromise(str1), returnsAPromise(str2)]);

// Then, Operation C runs
console.log(`With multiple awaits, we can use the variables
directly: ${promiseResult1} AND ${promiseResult2}`);
}
multipleAwaits(myFirstString, mySecondString);

Error Handling Using Catch & Finally

Error handling with promises and async/await is fairly similar. For successfully resolved promises, we’ll use then or try. For rejected promises, we’ll use catch. For code that we want to run after a promise has been handled, regardless of whether it was resolved or rejected, we’ll use finally:

Promises

let isOurPromiseFinished = false;
returnsAPromise(str)
.then(res => {
// If the promise resolves, we enter this code block
console.log(`using promise chains, ${res}`);
})
.catch(err => {
/* If the promise rejects, or if any of the code in our .then
statement throws, we enter this code block */
console.log(err);
})
.finally(() => {
/* This is for code that doesn't rely on the outcome of the
promise but still needs to run once it's handled */
isOurPromiseFinished = true;
})

Async/Await

let isOurPromiseFinished = false;const myAsyncAwaitBlock = async (str) => {
try {
// If the promise resolves, we enter this code block
const myPromise = await returnsAPromise(str);
console.log(`using async/await, ${res}`);
} catch(err) {
// If the promise rejects, we enter this code block
console.log(err);
} finally {
/* This is for code that doesn't rely on the outcome of the
promise but still needs to run once it's handled */
isOurPromiseFinished = true;
}
}
myAsyncAwaitBlock(myFirstString);

Summary

But really…which one should I use???

While promise chains and async/await are both built on promises, the syntactic differences between them can have real effects on your code’s logic and layout.

Promise chains are probably still a good option if you’re trying to quickly and concisely grab the results from a promise, using them in one or two different ways. Promise chains will allow you to avoid writing a bunch of unnecessary wrapper functions when a simple .then will do.

Async/await is an excellent option if you find yourself writing long, complicated waterfalls of .then statements. Async/await will allow you to clean it up into one readable, asynchronous callback function.

Cat tax.

--

--

Loves code, cats and coffee. 🤓☕️ Web engineer at The New York Times. Previously at Quartz and Mic. Career changer. List maker. Overthinker.