Search code examples
expresspromisees6-promiseexpress-router

How to gracefully handle promise rejection in express


I have the following express controller

class ThingsController {

  static async index(req, res, next) {
    try {
      const things = await Thing.all();
      res.json(things);
    } catch(err) {
      next(err);
    }  
  }
}

and router

router.route('/things').get(ThingsController.index)

In my app I plan to have several controllers which use promises to render the result

I do not want to repeat try/catch block every time

My first solution was to extract this logic into handle promise rejection function:

const handlePromiseRejection = (handler) =>

  async (req, res, next) => {
    try{
      await handler(req, res, next);
    } catch(err) {
      next(err);
    };
  };

and now we can remove try/catch block from the ThingsController.index and need to change router to this:

router.route('/things')
  .get(handlePromiseRejection(ThingsController.index))

But adding handlePromiseRejection on every route might be tedious task and I would want to have more clever solution.

Do you have any ideas?


Solution

  • Normal way of handling errors with async/await in routes is to catch the errors and pass it to the catch all error handler:

    app.use(async (req, res) => {
      try {
        const user = await someAction();
      } catch (err) {
        // pass to error handler
        next(err)
      }
    });
    
    app.use((err, req, res, next) => {
      // handle error here
      console.error(err);
    });
    

    With express-async-errors package, you could simply throw (or not worry about error thrown from some function). From docs: Instead of patching all methods on an express Router, it wraps the Layer#handle property in one place, leaving all the rest of the express guts intact.

    Usage is simple:

    require('express-async-errors'); // just require!
    app.use(async (req, res) => {
      const user = await User.findByToken(req.get('authorization')); // could possibly throw error, implicitly does catch and next(err) for you
    
      // throw some error and let it be implicitly handled !!
      if (!user) throw Error("access denied");
    });
    
    app.use((err, req, res, next) => {
      // handle error
      console.error(err);
    });