Refactoring Promise Chains w/Async-Await
In my never ending journey of refactoring and refining my code my most recent endeavor has been in writing cleaner and easier to read code with the help of
async-await

Up until recently I had only used JavaScript & Promises
primarily within the context of writing React code. As far as asynchronous code and Promises
go, the only use-cases I had for them were in writing fetch()
calls to an API. It would usually look something like this:
function getUsers(){
fetch('https://some-url-here.com/')
.then(resp => resp.json()
.then(data => someFunctionToUseReturnedData(data))
.catch(err => console.log(err))
}
Anyone familiar with the code above knows that JavaScript’s fetch API makes use of Promises
& by chaining subsequent Promises
together you achieve asynchronous functionality. Calling fetch()
returns a Promise
, as does any subsequent .then()
or .catch()
call, which then each have to handled. The ‘lifecycle’ of a Promise
is show below for reference if you need a quick refresher:

A simple API call like this is easy to read and understand however once your logic and use-cases grow in complexity the ‘readability’ factor can easily get away from you. This became exactly the case once I started working with Node/Express/MongoDB as a stack for my backend API. Dealing with complex collections and documents within MongoDB and handling their associations, chaining Promises
within controller functions often times quickly grew confusing and unreadable.
So I resolved to finally make use of theasync-await
keywords and refactor all of my existing code to read more synchronously. Let’s start with that simple API fetch call above and then I’ll go into some greater detail and another use-case.
First off every function has to be declared to be asynchronous right off the bat:
async function getUsers(){
...
}
This declares that zero or more asynchronous await
expressions will happen within the context of the function. Next, to make use of error handling we won’t be using .catch()
anymore so we instead are able to wrap our function’s inner code in a try/catch
block.
async function getUsers(){
try{
...
} catch(error){
console.log(error)
}
}
Next, we can finally refactor the .then()
block of our Promise
chain by using the await
keyword. The biggest thing to understand here is that the await
keyword is blocking. It blocks the execution of the rest of the code until the Promise
-based functionality that is happening ‘behind the scenes’ is completed. So keeping that in mind, definitely be weary of how many await
keywords you use within an asynchronous function. More often then not only when the returned result of an await
expression depends on the one before should using multiple awaits
be necessary. Usually, if you have many awaits
within one async
function this can be abstracted into smaller async
functions which could potentially run parallel to each other, therefore speeding up your code execution.
Now for the final change in our small code:
async function getUsers(){
try{
const resp = await fetch('https://some-url-here.com/');
const data = await resp.json();
someFunctionToUseReturnedData(data);
} catch(error){
console.log(error)
}
}
We now have the exact same functionality as we did before but written without chaining Promises and in a more succinct, synchronous way that can be easier for people not familiar with your code could understand.
Just to show a quick example of how this can be utilized in a backend Node.js environment as well, I’ll include an example of a Promise
chain (req/res)
& then a refactored version using the async-await
keywords
const Post = require('../models/post')
const express = require('express')
const router = express.Router();...router.get('/post/:id', (req, res, next) => {
Post.findById(req.params.id)
.then(post => {
if (!post){
const error = new Error('Could not find post');
error.statusCode = 404;
throw error;
} else {
res.status(200).json({
post: post
});
}
});
.catch(error => {
if (!error.statusCode){
error.statusCode = 500;
}
next(error);
})
})
Here we have a Promise
chain to fetch a post from a database and to either send back the post in json format or to send back an error describing either that the post couldn’t be found, or that there was a server error. Now lets go ahead and rewrite it using the async-await
keywords
router.get('/post/:id', async (req, res, next) => {
try {
const post = await Post.findById(req.params.id)
if (!post){
const err = new Error('Could not find post');
err.statusCode = 404;
throw err;
} else {
res.status(200).json({
post: post
})
}
} catch (error) {
if (!error.statusCode) error.statusCode = 500;
next(error);
}
})
The code behaves the same as it did before but without the Promise
chaining and by utilizing async-await
it becomes more readable. The simple await
expression makes for more synchronous written code.
Refactoring away from Promise chaining helps with readability especially when working on a large and growing application. If you’d like to do more research on async-await, Promises or the Fetch API, below are links to documentation.
[1]: async Function Docs(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)
[2]: Promise Docs (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
[3]: Fetch API Docs (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)