Skip to main content

Error handling in Express with Async/Await routes

3 min read

Async/Await for me is one of the best things to not quite happen to JavaScript yet.

As we all know from Object.observe, you're never really sure what's going to end up in a language until it actually lands.

But I've been transpiling async/await for a while now and I love it.

It makes asynchronous code look great and really sorts out any issues with scoping of variables and nested promises getting out of hand.

Error handling with async/await generally takes the form of wrapping everything in a try/catch.

async function() {
	try {
	    const result = await goDoSomething();
	    return { result.data };
	} catch(err) {
	    console.error(err);
	    return {};
	}
}

In Express, you'll want to be passing the errors that get thrown to an error handler middleware sitting at the end of your stack. But just how would you go about doing this with an async route?

Well this is the obvious option:

app.get("/", async (req, res, next) => {
  try {
    const result = await goDoSomething();
    return result.data;
  } catch (err) {
    next(err);
  }
});

app.use((err, req, res, next) => {
  /* do something with the error */
  console.error(err);
});

This can get a bit cumbersome when dealing with a lot of routes, doing the same try catches over and over again.

What if we could do it automatically for each route?

Well, because an async function returns a promise, we can hook onto the catch function and catch those errors from the promise. We can create a wrapper that will provide the error handling for us.

What would that look like?

function catchAsyncErrors(fn) {
  return (req, res, next) => {
    const routePromise = fn(req, res, next);
    if (routePromise.catch) {
      routePromise.catch((err) => next(err));
    }
  };
}

This piece of code takes an async route function as a parameter, and returns a function with the signature req, res, next.

Essentially, it takes a route and returns a route with a catch handler.

What happens when the route is called?

The fn(req, res, next) passes the request, response and next into our original route function and this returns a promise.

We then check if that returned object has a catch attribute on it (if so, we know it's a promise) and we can now hook into the catch function of the promise and call next(err) to pass it directly to our error handling middleware.

So altogether, taking the example above, this could look something like:

function catchAsyncErrors(fn) {
    return (req, res, next) => {
		const routePromise = fn(req, res, next);
		if (routePromise.catch) {
		    routePromise.catch(err => next(err));
		}
    }
}

async function asyncRoute(req, res, next)  {
	    const result = await goDoSomething();
	    return result.data;
});

app.get('/', catchAsyncErrors(asyncRoute));

app.use((err, req, res, next) => {
    /* do something with the error */
    console.error(err);
});

This is a great way to not have to worry about try/catching every time you make an async call and the errors bubble up to this point every time during your route execution.

I've encapsulated this in a module which can be found here https://github.com/madole/async-error-catcher

    © 2021 by Madole.
    GitHub Repository
    Last build: 1731364498764