Ioannis Ioannou
HomeAbout MeContact

Using async/await in Express.js

Ioannis Ioannou
Ioannis Ioannou
April 28, 2021
1 min
Using async/await in Express.js

Express.js is one of the most popular Node.js frameworks. Unfortunately, though, it doesn’t support async/await handlers. This functionality will be supported in Express 5.0, but there is no scheduled released date yet.

First Attempt

In our first attempt to use async/await in Express.js, we use an async function for the handler.

const express = require('express')
const app = express()

const asyncFunc = () => {
  return new Promise((resolve) => {
    setTimeout(() => resolve("Hello World!"), 1000)
  })
}

app.get('/', async (req, res) => {
  const result = await asyncFunc()
  return res.send(result)
})

app.listen(3000, () => console.log('Start listening'))

Opening http://localhost:3000 in the browser prints Hello World! as expected.

So what’s the issue? The issue with our approach is when the asyncFunc returns an error.

const asyncFunc = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error('Failed!')), 1000)
  })
}

If we replace the asyncFunc with the one given above and refresh the page, we notice that the page keeps loading forever. Checking the logs of the server we notice an unhandled promise rejection error. This is because express doesn’t automatically handle the rejected promise, and a response is never sent to the client.

Handling Errors

In order to handle the rejected promise we can wrap the body of the handler in try/catch and call the next function with the thrown error. Given that we don’t define any error handlers, the error passed to the next function will be handled by the default express error handler.

app.get('/', async (req, res, next) => {
  try {
    const result = await asyncFunc()
    return res.send(result)
  } catch (err) {
    next(err)
  }
})

Loading again the page, we can see now the error printed in the browser.

We can improve our code by extracting a reusable function.

const asyncHandler = (func) => (req, res, next) => {
  Promise.resolve(func(req, res, next))
    .catch(next)
}

app.get('/', asyncHandler(async (req, res) => {
  const result = await asyncFunc()
  return res.send(result)
}))

The asyncHandler is a higher order function which gets a handler function as an input and returns a handler function. The returned handler catches any exceptions thrown by the input handler and calls the next express function with the thrown error. Given that func is wrapped using Promise.resolve(), the asyncHandler can also be used with non async functions.

Complete Example

const express = require('express')
const app = express()

const asyncHandler = (fun) => (req, res, next) => {
  Promise.resolve(fun(req, res, next))
    .catch(next)
}

const asyncFunc = (text) => {
  return new Promise((resolve) => {
    setTimeout(() => resolve(text), 1000)
  })
}

app.get('/', asyncHandler(async (req, res) => {
  const result1 = await asyncFunc('Hello,')
  const [result2, result3] = await Promise.all([
    asyncFunc('my name is'),
    asyncFunc('Ioannis')
  ])
  const result = `${result1} ${result2} ${result3}`
  return res.send(result)
}))

app.listen(3000, () => console.log('Start listening'))

Summary

In this post, we discussed how we can use async/await in Express.js, and we extracted a reusable function to support this. As an alternative to the reusable function given here, you can use some popular npm packages which offer the same functionality. For example, express-async-errors, express-async-handler or express-promise-router.


Tags

#node.js
Ioannis Ioannou © 2021