11

这两个中间件函数的行为不同,我不知道为什么:

在这里,错误将被 try/catch 捕获:

router.get('/force_async_error/0',  async function (req, res, next) {
  try{
    await Promise.reject(new Error('my zoom 0'));
  }
  catch(err){
    next(err);
  }
});

但是在这里,错误不会被 try/catch 捕获:

router.get('/force_async_error/1', async function (req, res, next) {
  await Promise.reject(new Error('my zoom 1'));
});

我认为 Express 用 try/catch 包装了所有中间件函数,所以我看不出它的行为会有什么不同?

我查看了 Express 源代码,处理程序如下所示:

Layer.prototype.handle_request = function handle(req, res, next) {
  var fn = this.handle;

  if (fn.length > 3) {
    // not a standard request handler
    return next();
  }

  try {
    fn(req, res, next); // shouldn't this trap the async/await error?
  } catch (err) {
    next(err);
  }
};

那么为什么那里的 try/catch 不捕获抛出的错误呢?

4

3 回答 3

11

即使您已经接受了另一个答案,我也会在这里添加一个答案,因为我认为这里发生的事情可以更好地解释,这将有助于其他人试图理解这一点。

在您的代码中:

router.get('/force_async_error/1', async function (req, res, next) {
    await Promise.reject(new Error('my zoom 1'));
});

让我们讨论一下发生了什么:

async首先,您声明了为了在其中使用而必须执行的回调await。一个async函数告诉解释器做几件重要的事情。

1. 异步函数总是返回一个 Promise。 承诺的解析值将是函数返回的任何值。

2. async 函数在内部用try/catch. 如果在函数代码的顶级范围内抛出任何异常,那么这些异常将被捕获并自动拒绝函数返回的承诺。

3. 异步功能允许您使用await. 这是对解释器的一个指示,它应该实现并允许await函数内部的语法。这与上面的前两点有关,这就是为什么您不能await在任何 'ol 函数中使用。任何未捕获的拒绝await也将拒绝函数返回的承诺。

重要的是要理解,虽然async/await语法允许您编写带有异常的程序并像同步代码一样尝试/捕获,但它并不完全相同。该函数仍会立即返回一个承诺,并且函数中未捕获的异常会导致该承诺在稍后的某个时间被拒绝。它们不会导致同步异常冒泡到调用者。因此,Expresstry/catch不会看到同步异常。

但是在这里,错误不会被 try/catch 捕获

我认为 Express 用 try/catch 包装了所有中间件函数,所以我看不出它的行为会有什么不同?

那么为什么那里的 try/catch [in Express] 没有捕获抛出的错误?

这有两个原因:

  1. 被拒绝的承诺不是同步抛出,因此 Express 无法通过 try/catch 捕获它。该函数只返回一个被拒绝的承诺。

  2. Express 根本没有查看路由处理程序回调的返回值(您可以在您显示的 Express 代码中看到)。因此,您的async函数返回一个后来被拒绝的承诺这一事实完全被 Express 忽略了。它只是这样做fn(req, res, next);并且不注意返回的承诺。因此,拒绝承诺被置若罔闻。

有一个类似于 Express 的框架叫做Koa,它大量使用了 Promise,并且确实注意返回的 Promise,它会看到你拒绝的 Promise。但是,这不是 Express 所做的。


如果你想在 Express 中实现一些 Koa 类型的功能,你可以自己实现它。为了让其他功能不受干扰,使其能够正常工作,我将实现一个名为的新方法getAsync,该方法确实使用了 Promise:

router.getAsync = function(...args) {
    let fn = args.pop();
    // replace route with our own route wrapper
    args.push(function(req, res, next) {
        let p = fn(req, res, next);
        // if it looks like a promise was returned here
        if (p && typeof p.catch === "function") {
            p.catch(err => {
                next(err);
            });
        }
    });
    return router.get(...args);
}

然后你可以这样做:

router.getAsync('/force_async_error/1', async function (req, res, next) {
  await Promise.reject(new Error('my zoom 1'));
});

而且,它会正确调用next(err)您的错误。

或者,您的代码甚至可以是这样的:

router.getAsync('/force_async_error/1', function (req, res, next) {
  return Promise.reject(new Error('my zoom 1'));
});

PS 在完整的实现中,您可能会制作一堆动词的异步版本,然后为中间件实现它,然后将其放在路由器原型上。但是,这个例子是向你展示它是如何工作的,而不是在这里做一个完整的实现。

于 2018-03-22T01:05:13.787 回答
4

这是因为调用是异步的,请使用以下代码:

try {
  console.log('Before setTimeout')
  setTimeout(() => {
    throw new Error('Oups')
  })
  console.log('After setTimeout')
}
catch(err) {
  console.log('Caught', err)
}
console.log("Point of non-return, I can't handle anything anymore")

如果你运行它,你应该会看到错误是在Point of non-return. 当我们throw排队时为时已晚,我们在try/之外catch。此时如果抛出错误,它将未被捕获。

您可以通过在调用者async中使用/来解决此问题(对被调用者无关紧要),即:await

void async function () {
  try {
    console.log('Before setTimeout')
    await new Promise((resolve, reject) =>
      setTimeout(() => {
        reject(new Error('Oups'))
      })
    )
    console.log('After setTimeout')
  }
  catch(err) {
    console.log('Caught', err.stack)
  }
  console.log("Point of non-return, I can't handle anything anymore")
}()

最后,这意味着要让 Express 处理异步错误,您需要将代码更改为:

async function handle(req, res, next) {
  // [...]
  try {
    await fn(req, res, next); // shouldn't this trap the async/await error?
  } catch (err) {
    next(err);
  }
}

更好的解决方法

定义一个wrap这样的函数:

const wrap = fn => (...args) => Promise
    .resolve(fn(...args))
    .catch(args[2])

并像这样使用它:

app.get('/', wrap(async () => {
  await Promise.reject('It crashes!')
}))
于 2018-03-21T22:50:04.367 回答
0

这些都没有真正回答这个问题,如果我理解正确的话:

由于 async/await 语法允许您使用非异步样式 try/catch 语法处理被拒绝的“await”,为什么 Express 的 try/catch 不能在顶层处理失败的“await”并变成 500 for你?

我相信答案是 Express 内部调用您的任何函数也必须使用“async”声明并使用“await”调用您的处理程序以启用异步捕获 try/catch 以在该级别工作。

想知道 Express 团队是否有功能请求?他们只需要在两个地方添加两个关键字。如果成功,则什么也不做,如果异常移交给错误处理堆栈。

于 2018-12-19T01:02:47.173 回答