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.
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.
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.
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'))
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.