为什么从 bluebird promise 中的异步函数中抛出的错误没有在 .catch() 函数中捕获?

Why is an error thrown from within an async function inside a bluebird promise not caught in the .catch() function?

在下面的代码示例中,函数 baz() 抛出 TypeError,当在 fs.open 回调中调用 new Promise 回调时,节点进程立即以非零值退出,并且永远不会捕获异常。

var Promise = require('bluebird');
var fs = require('fs');

function baz() {
  [].toDateString(); // should throw type error
}

function bar() {
  return new Promise((resolve, reject) => {
    fs.open('myfile', 'r', (err, fd) => {
      baz();  // throws TypeError [].toDateString which is never caught.
      return resolve('done');
    });
  })
  .catch((error) => {
    console.log('caught errror');
    console.log(error);
    return Promise.resolve(error);
  });
}

bar()
  .then((ret) => {
    console.log('done');
  });

输出:

 $ node promise_test.js
 /Users/.../promise_test.js:5
 [].toDateString(); // should throw type error
    ^

 TypeError: [].toDateString is not a function
   at baz (/Users/..../promise_test.js:5:6)
   at fs.open (/Users/..../promise_test.js:12:7)
   at FSReqWrap.oncomplete (fs.js:123:15)
✘-1 ~/

如果我稍微修改此代码以在 promise 回调中抛出异常,但在 fs.open 回调之外,异常将按预期捕获并继续执行:

return new Promise((resolve, reject) => {
 baz();  // throws TypeError [].toDateString
 fs.open('myfile', 'r', (err, fd) => {
   console.log(err);
   return resolve('done');
 });

输出:

$ node promise_test.js
  caught errror
  TypeError: [].toDateString is not a function
  ...
  done

这篇文章似乎提供了一些关于承诺吞噬异常的指导,以及 BlueBird 如何帮助处理它们:

Promise.onPossiblyUnhandledRejection(function(error){
    throw error;
});

And on the odd chance you do want to discard a rejection, just handle it with an empty catch, like so:

Promise.reject('error value').catch(function() {});

http://jamesknelson.com/are-es6-promises-swallowing-your-errors/

因为异常发生在 fs.open() 异步回调中,所以异常会返回到 fs.open() 中调用完成回调的异步事件处理程序,然后它会消失并且没有机会传播任何地方。蓝鸟永远没有机会看到它。

这是一个教科书示例,说明为什么不应将常规异步回调代码与 promise 代码混合使用。相反,promisify fs.open() 并使用 promisified 版本,然后异常将被 promise 基础设施适当地捕获。

fs.openAsync = function(fname, mode) {
    return new Promise(function(resolve, reject) {
        fs.open(fname, mode, function(err, fd) {
            if (err) return reject(err);
            resolve(fd);
        });
    });
}

function bar() {
  return fs.openAsync('myfile', 'r').then(function(fd) {
      baz();  // throws TypeError [].toDateString which will now be caught
              // in the .catch() below
  }).catch(function(err) {
    // handle error here
  });
}

或者,在 Bluebird 中,您可以使用内置的 promisify 函数:

const fs = Promise.promisifyAll(require('fs'));

这将自动创建 fs.openAsync(),其中 returns 所有其他异步方法的承诺和承诺版本。


仅供参考,promise 基础结构只能捕获由 promise 基础结构本身调用的回调中的异常。它通过将对每个回调的调用包装在它自己的 try/catch 中来实现。正如您在代码中看到的,您正在直接使用 fs.open() 回调,它没有这样的机会被包裹在这样的 try/catch 处理程序中以捕获异常并将其变成拒绝。因此,相反,通常的解决方案是创建一个 fs.open() 的 promisified 版本,它立即在回调中拒绝或解决,然后您的自定义回调代码进入 .then() 处理程序,其中回调被包装并且异常将被抓住并自动变成拒绝。