3

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?

Hirurg103
  • 3,515
  • 1
  • 21
  • 44

2 Answers2

5

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);
});
1565986223
  • 5,222
  • 2
  • 10
  • 27
  • Actually it worked for me without the need to throwing an error manually. I have tests which check that app replies with 500 on promise rejection and they pass without the need to throw an error in a handler. Express has out of the box error handler which responds with 500 in case of an error – Hirurg103 Apr 04 '19 at 11:40
  • 1
    `if (!user) throw Error("access denied");` If you're refering to this line, author means you can throw error in your route instead of doing `next(error)`, the package implicitly does this for you. Just an example that you can throw error. – 1565986223 Apr 04 '19 at 14:51
0

So, that's how you handle promise rejection if you really want to handle on each route.

Here's a single liner ES6 version of it as well.

Code

const handlePromiseRejection = (handler) => (req, res, next) => handler(req, res, next).catch(next)

Though like you asked, the easiest way is to listen to the unhandledRejection on the process using on your index.js or app.js

process.on("unhandledRejection", (error, promise) => {
  console.log("Unhandled Rejection at:", promise, "reason:", reason);
  // Application specific logging, throwing an error, or other logic here
});

From Node.js

Asghar Musani
  • 368
  • 1
  • 18
  • The `handler(req, res, next).catch(next)` version of `handlePromiseRejection` won't work in case if the handler is not an async function (if we apply `handlePromiseRejection`on every route handler in the app) – Hirurg103 Apr 04 '19 at 11:07
  • True but the OP asked about handling Promise rejection with a universal approach. – Asghar Musani Apr 04 '19 at 19:28
  • the problem with your solution is that my browser hangs forever when I request an API endpoint and it fails with an unhandled promise rejection – Hirurg103 Apr 09 '19 at 09:53