为什么在蓝鸟承诺中的异步函数中抛出的错误未在.catch()函数中捕获?

时间:2016-10-02 20:09:36

标签: javascript node.js promise bluebird

在下面的代码示例中,函数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

2 个答案:

答案 0 :(得分:3)

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

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

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(),它会返回所有其他异步方法的promise和promisified版本。

仅供参考,承诺基础设施只能捕获由承诺基础设施本身调用的回调中的异常。它通过将调用包装在它自己的try / catch中来实现。正如您在代码中看到的那样,您正在直接使用fs.open()回调,这种回调没有机会被包含在这样的try / catch处理程序中以捕获异常并将其转换为拒绝。所以,通常的解决方案是创建一个fs.open()的promisified版本,它立即拒绝或解析回调,然后你的自定义回调代码进入回调被包装的.then()处理程序,异常将被抓住并自动变成拒绝。

答案 1 :(得分:0)

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

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

奇怪的是,你确实想放弃拒绝,只需处理   它有一个空的捕获,像这样:

     

Promise.reject('错误值')。catch(function(){});

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