Express中间件无法捕获async / await引发的错误,但为什么呢?

时间:2018-03-21 22:30:33

标签: node.js express async-await es6-promise express-4

这两个中间件功能表现不同,我无法弄清楚原因:

这里,错误将被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不会捕获抛出的错误?

3 个答案:

答案 0 :(得分:6)

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

在您的代码中:

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

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

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

<强> 1。异步函数总是返回一个promise。 promise的解析值将是函数返回的任何值。

<强> 2。异步函数在内部用try/catch包装。如果在函数代码的顶级范围内抛出任何异常,那么这些异常将被捕获并自动拒绝该函数返回的promise。

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

重要的是要理解虽然async/await语法允许您使用异常编程,并尝试/捕获类似同步代码,但它并不完全相同。该函数仍然立即返回一个promise,函数中未捕获的异常会导致promise在以后的某个时间被拒绝。它们不会导致同步异常冒泡到调用者。因此,Express try/catch将不会看到同步异常。

  

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

困住      

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

     

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

这有两个原因:

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

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

  3. 有一个名为Koa的类似Express的框架,它使用许多承诺,并且会注意返回的承诺,并且会看到你被拒绝的承诺。但是,这不是Express所做的。

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

    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'));
    });
    

    P.S。在完整的实现中,您可能会制作一堆动词的异步版本,并且您将为中间件实现它,并且您将它放在路由器原型上。但是,这个例子是为了告诉你如何工作,而不是在这里完成一个完整的实现。

答案 1 :(得分:3)

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

&#13;
&#13;
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")
&#13;
&#13;
&#13;

如果您运行它,您应该会在Point of non-return之后看到错误被触发。 当我们在throw行时已经太晚了,我们就在try / catch之外了。此时如果发生错误,它将被删除。

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

&#13;
&#13;
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")
}()
&#13;
&#13;
&#13;

最后,这意味着要使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!')
}))

答案 2 :(得分:1)

这些都不能真正回答这个问题,如果我理解正确,那就是:

由于async / await语法使您可以使用非异步样式try / catch语法处理被拒绝的“唤醒”,所以为什么Express的try / catch不会处理失败的“ await”顶层,变成500个了吗?

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

想知道Express团队是否有功能要求吗?他们只需要在两个位置添加两个关键字即可。如果成功,则不执行任何操作;如果异常,则移至错误处理堆栈。