Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
151 lines (108 sloc) 5.54 KB

Arguably the biggest gap in Express' API is its non-obvious lack of support for async/await. This functionality is scheduled to land in Express 5.0, but the Express 5.0 GitHub PR has been open since 2014 and as of this writing there's no scheduled release date. To avoid having to wrap() every single middleware and route handler, I'm releasing the @awaitjs/express module. It adds getAsync(), useAsync(), and other helpers to Express apps to add full support for async/await. It even supports async error handling middleware.

Getting Started

To install:

npm install @awaitjs/express

The @awaitjs/express module exports two functions, decorateApp() and wrap(). The decorateApp() function is what you generally want to use, because that's what adds useAsync(), getAsync(), etc. to your app.

const express = require('express');
const { decorateApp } = require('@awaitjs/express');

const app = decorateApp(express());

// Important to use `getAsync()` here, otherwise this middleware will hang
// forever with Express 4.x.
app.getAsync('*', async function(req, res, next) {
  throw new Error('Oops!');
});

app.use(function(error, req, res, next) {
  res.send(error.message);
});

No Need to Call next()

The @awaitjs/express module frees you from having to call next() in Express middleware. Once your async function is fulfilled, the module will call next() for you automatically. You can still call next(), but you don't have to.

const express = require('express');
const { decorateApp } = require('@awaitjs/express');

const app = decorateApp(express());

app.useAsync(async function() {
  // No need to call `next()`. You can if you want to.
  await new Promise(resolve => setTimeout(resolve, 250));
});

app.get(function(req, res) {
  res.send('Hello, World');
});

Async Error Handling Middleware

Many existing solutions don't correctly handle async error handling middleware. For example, the latest release v1.1.4 of the popular express-async-handler package doesn't handle async error handling middleware correctly as shown in the below example.

const app = require('express')();
const assert = require('assert');
const asyncHandler = require('express-async-handler');
const superagent = require('superagent');

run().catch(error => console.error(error.stack));

async function run() {
  app.use(asyncHandler(async function(req, res, next) {
    throw new Error('Oops!');
  }));

  app.get('/', asyncHandler(async function(req, res) {
    await new Promise(resolve => setTimeout(resolve, 10));
    res.send('Hello, World!');
  }));

  app.use(asyncHandler(async function(error, req, res, next) {
    throw new Error('foo');
  }));

  app.use(function(error, req, res, next) {
    res.send(error.message);
  });

  const server = await app.listen(3000);

  const res = await superagent.get('http://localhost:3000');

  // The below fails! express-async-handler doesn't support async
  // error handlers,
  // see https://github.com/Abazhenov/express-async-handler/issues/22
  assert.equal(res.text, 'foo');

  await server.close();
}

Thankfully, @awaitjs/express handles this detail correctly.

const assert = require('assert');
const express = require('express');
const { decorateApp } = require('@awaitjs/express');
const superagent = require('superagent');

run().catch(error => console.error(error.stack));

async function run() {
  const app = decorateApp(express());

  app.useAsync(async function(req, res, next) {
    throw new Error('Oops!');
  });

  app.getAsync('/', async function(req, res) {
    await new Promise(resolve => setTimeout(resolve, 10));
    res.send('Hello, World!');
  });

  app.useAsync(async function(error, req, res, next) {
    throw new Error('foo');
  });

  app.use(function(error, req, res, next) {
    res.send(error.message);
  });

  const server = await app.listen(3000);

  const res = await superagent.get('http://localhost:3000');

  // Succeeds!
  assert.equal(res.text, 'foo');

  await server.close();
}

Moving On

Getting Express to correctly support async/await is tricky. Even the most popular module at the time of this writing doesn't quite get everything right. So instead of copy/pasting a dubious wrap() everywhere, download @awaitjs/express and start using async functions with Express.

Surprised that Express doesn't support async/await in general? Want to know how to determine whether your favorite npm modules support async/await without reconciling contradictory answers on StackOverflow? Chapter 4 of Mastering Async/Await explains the core principles for determining whether a given library or framework supports async/await, so get the ebook today!