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

时间:2016-09-26 08:48:48

标签: node.js promise bluebird

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

为了向被调用者一致地报告任何错误,我想规范化所有错误对象以遵循通用格式。但我不知道如何使用Bluebird和/或标准的promise 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个调用,代码的金字塔形状肯定会开始看起来像回调地狱......
  4. 编辑:为了更加清晰,这里是我设想的解决方案,但无法实现: chain with parallel path for errors & ok results

4 个答案:

答案 0 :(得分:1)

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

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

阅读您的最新评论我猜后者可能更适合您的需求。

答案 1 :(得分:1)

您可以使用bluebird过滤捕获: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
});

答案 2 :(得分:1)

您正在寻找的解决方案是

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

您可以将其与图书馆方法的宣传结合起来。

答案 3 :(得分:0)

作为替代解决方案,使用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);