最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - express.js async router and error handling - Stack Overflow

programmeradmin0浏览0评论

I have an async function as a route handler, and i'd like to have errors handled as some kind of middleware. Here is my working attempt:

router.get(
  "/",
  asyncMiddleware(
    routeProviderMiddleware(
      async ({ y }) => ({
        body: await db.query({x: y})
      })
    )
  )
)

// This is the middleware that catches any errors from the business logic and calls next to render the error page
const asyncMiddleware = fn =>
  (req, res, next) => {
    Promise.resolve(fn(req, res, next))
      .catch(next)
  }

// This is a middleware that provides the route handler with the query and maybe some other services that I don't want the route handler to explicitly access to
const routeProviderMiddleware = routeHandlerFn => async (req, res) => {
  const {status = 200, body = {}} = await routeHandlerFn(req.query)
  res.status(status).json(body)
}

What I strive to is a way to make the route declaration cleaner - I don't want the 2 middleware wrappers there, ideally i'd like for the business logic function there only, and somehow declare that every route is wrapped in these. Even bining the two middlewares together would be nice, but I didn't manage.

I have an async function as a route handler, and i'd like to have errors handled as some kind of middleware. Here is my working attempt:

router.get(
  "/",
  asyncMiddleware(
    routeProviderMiddleware(
      async ({ y }) => ({
        body: await db.query({x: y})
      })
    )
  )
)

// This is the middleware that catches any errors from the business logic and calls next to render the error page
const asyncMiddleware = fn =>
  (req, res, next) => {
    Promise.resolve(fn(req, res, next))
      .catch(next)
  }

// This is a middleware that provides the route handler with the query and maybe some other services that I don't want the route handler to explicitly access to
const routeProviderMiddleware = routeHandlerFn => async (req, res) => {
  const {status = 200, body = {}} = await routeHandlerFn(req.query)
  res.status(status).json(body)
}

What I strive to is a way to make the route declaration cleaner - I don't want the 2 middleware wrappers there, ideally i'd like for the business logic function there only, and somehow declare that every route is wrapped in these. Even bining the two middlewares together would be nice, but I didn't manage.

Share Improve this question asked Apr 28, 2019 at 7:13 JenianJenian 5921 gold badge6 silver badges20 bronze badges 2
  • You might be interested in npmjs./package/express-promise-router which would allow you to leave out the asyncMiddleware function. – Sidney Commented Apr 28, 2019 at 7:30
  • 1 Please check this medium./@Abazhenov/… – rab Commented Apr 28, 2019 at 7:38
Add a ment  | 

5 Answers 5

Reset to default 3

I use following approach:

Create asyncWrap as helper middleware:

const asyncWrap = fn =>
  function asyncUtilWrap (req, res, next, ...args) {
    const fnReturn = fn(req, res, next, ...args)
    return Promise.resolve(fnReturn).catch(next)
  }

module.exports = asyncWrap

All your routes/middlewares/controllers should use this asyncWrap to handle errors:

router.get('/', asyncWrap(async (req, res, next) => {
  let result = await db.query({x: y})
  res.send(result)
}));

At app.js, the last middleware will receive the errors of all asyncWrap:

// 500 Internal Errors
app.use((err, req, res, next) => {
  res.status(err.status || 500)
  res.send({
    message: err.message,
    errors: err.errors,
  })
})

Express 5 automatically handles async errors correctly

https://expressjs./en/guide/error-handling.html currently says it clearly:

Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error. For example:

app.get('/user/:id', async function (req, res, next) {
 var user = await getUserById(req.params.id)
 res.send(user)
})

If getUserById throws an error or rejects, next will be called with either the thrown error or the rejected value. If no rejected value is provided, next will be called with a default Error object provided by the Express router.

I have shown that in an experiment at: Passing in Async functions to Node.js Express.js router

This means that you will be able to just make the callback async and use await from it directly without any extra wrappers:

router.get("/", async (req, res) => 
  const obj = await db.query({x: req.params.id})
  // Use obj normally.
)

and errors will be correctly handled automatically.

Express permits a list of middlewares for a route and this approach sometimes works for me better than higher-order functions (they sometimes look like an overengineering).

Example:

app.get('/',
  validate,
  process,
  serveJson)

function validate(req, res, next) {
  const query = req.query;
  if (isEmpty(query)) {
    return res.status(400).end();
  }
  res.locals.y = query;
  next();
}

function process(req, res, next) {
  Promise.resolve()
  .then(async () => {
    res.locals.data = await db.query({x: res.locals.y});
    next();
  })
  .catch((err) =>
    res.status(503).end()
  );
}

function serveJson(req, res, next) {
  res.status(200).json(res.locals.data);
}

What you can do is add an error handlers after your routes. https://expressjs./en/guide/error-handling.html

app.use(function (err, req, res, next) {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

What I ended up doing is unifying the wrappers like this:

const routeProvider = routeHandlerFn => async (req, res, next) => {
  try {
    const {status = 200, body = {}} = await routeHandlerFn(req.query)
    res.status(status).json(body)
  } catch(error) {
    next(error)
  }
}

This wrapper is all any route would need. It catches unexpected errors and provides the route handler with the needed params.

发布评论

评论列表(0)

  1. 暂无评论