如何规范化承诺链中的错误对象?

How to normalize error objects in a promise chain?

我想使用 promises 将调用链接到不同的库。如果失败,库方法 return 描述错误的对象,但具有不同的字段,具体取决于库。

为了始终如一地向被调用方报告任何错误,我想规范化所有错误对象以遵循通用格式。但我不知道如何使用 Bluebird and/or 标准承诺 API.

以优雅的方式做到这一点

在伪 js 中,这是我所拥有的:

var methodAFromLibX_Async = Promise.promisify(...);
var methodBFromLibY_Async = Promise.promisify(...);

methodAFromLibX_Async(...)
.then(function(result) {
  methodBFromLibY_Async(...)
  .then(function(result) { ... })
  .catch(normalizeAndSendErrorFromLibY);
})
.catch(normalizeAndSendErrorFromLibX);

上面的代码似乎可以工作,但是:

  1. 我在 normalizeAndSendErrorFromLibYnormalizeAndSendErrorFromLibX
  2. 之间有冗余代码
  3. 在我的真实用例中,我必须链接超过 2 个调用,代码的金字塔形状开始看起来像一个回调地狱...

编辑: 为了更清楚一点,这里是我设想的解决方案,但无法实现:

如果您想避免您在示例中展示的模式,似乎还有两个其他选项供您选择:

promisify 你的库如图所示,在你的链中正确传播错误,然后构建一个能够规范化 所有 已知错误的函数:

var methodAFromLibX_Async = Promise.promisify(...);
var methodBFromLibY_Async = Promise.promisify(...);

methodAFromLibX_Async(...)
.then(function(result) {
  return methodBFromLibY_Async(...)
  .then(function(result) { ... })
})
.catch(function(err){
  if (hasLibXShape(err)){
    return Promise.reject(normalizeLibXErr(err));
  } else if (hasLibYShape(err)){
    return Promise.reject(normalizeLibYErr(err));
  }
})
.catch(function(normalizedErr){
  // handle normalized error
});

另一种选择是手动承诺您的库方法并在此处规范化错误:

function promisifiedMethodA(/*...args*/){
   var args = [].slice.call(arguments);
   return new Promise(function(resolve, reject){
       methodA.apply(null, args.concat([function(err, data){
          if (err) return reject(normalizeMethodAError(err));
          resolve(data);
       }]));
   });
}

阅读您的最新评论,我想后者可能更符合您的需求。

您可以使用蓝鸟过滤捕获:http://bluebirdjs.com/docs/api/catch.html#filtered-catch

并将您的代码更改为如下内容:

var methodAFromLibX_Async = Promise.promisify(...);
var methodBFromLibY_Async = Promise.promisify(...);

methodAFromLibX_Async(...)
.then(function(result) {
   return methodBFromLibY_Async(...);
}).then(function(result) {
   ... 
}).catch({code: 'X_Async', message: 'bad lib X'}, function(e) {
  //If it is a methodAFromLibX_AsyncError, will end up here because

}).catch({code: 'Y_Async', message: 'bad lib Y'}, function(e) {
  //Will end up here if a was never declared at all

}).catch(function(e) {
   //Generic catch-the rest, error wasn't methodAFromLibX_AsyncError nor
   //methodBFromLibY_AsyncError
});

您正在寻找的解决方案是

return methodAFromLibX_Async(…)
.then(function(result) {
     return methodBFromLibY_Async(…)
     .then(function(result) {
          return methodCFromLibX_Async(…)
          .catch(normalizeAndThrowErrorFromLibX);
     }, normalizeAndThrowErrorFromLibY);
}, normalizeAndThrowErrorFromLibX)
.then(reportSuccess, reportError);

但这很难看。鉴于您的错误处理程序无论如何都会重新抛出错误,您也可以使用

return methodAFromLibX_Async(…)
.catch(normalizeAndThrowErrorFromLibX)
.then(function(result) {
     return methodBFromLibY_Async(…)
     .catch(normalizeAndThrowErrorFromLibY)
     .then(function(result) {
          return methodCFromLibX_Async(…)
          .catch(normalizeAndThrowErrorFromLibX);
     });
})
.then(reportSuccess, reportError);

这仍然不是最佳选择。您不想在每次调用这些函数时都加上 .catch(normalise),也不想被迫嵌套它们。因此,最好将它们每个都考虑到它们自己的功能中:

function withRejectionHandler(fn, normalise) {
     return function() {
         return fn.apply(this, arguments).catch(normalise);
     };
}
var methodA = withRejectionHandler(methodAFromLibX_Async, normalizeAndThrowErrorFromLibX);
var methodB = withRejectionHandler(methodBFromLibY_Async, normalizeAndThrowErrorFromLibY);
var methodA = withRejectionHandler(methodCFromLibX_Async, normalizeAndThrowErrorFromLibX);

return methodA(…).then(methodB).then(methodC).then(reportSuccess, reportError);

您可以将其与库方法的承诺结合起来。

作为替代解决方案,使用 Bluebird 的 Promise.coroutine:

/* Bergi's solution to normalize */
function withRejectionHandler(fn, normalise) {
     return function() {
         return fn.apply(this, arguments).catch(normalise);
     };
}
var methodA = withRejectionHandler(methodAFromLibX_Async, normalizeAndThrowErrorFromLibX);
var methodB = withRejectionHandler(methodBFromLibY_Async, normalizeAndThrowErrorFromLibY);
var methodA = withRejectionHandler(methodCFromLibX_Async, normalizeAndThrowErrorFromLibX);

/* use of coroutine to sequence the work */
var workflow = Promise.coroutine(function*() {
    var resA = yield methodA(...);
    var resB = yield methodB(...);
    var resC = yield methodC(...);

    reportSuccess(resA, resB, resC);
});

workflow().catch(reportError);