如何规范化承诺链中的错误对象?
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);
上面的代码似乎可以工作,但是:
- 我在
normalizeAndSendErrorFromLibY
和 normalizeAndSendErrorFromLibX
之间有冗余代码
- 在我的真实用例中,我必须链接超过 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);
我想使用 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);
上面的代码似乎可以工作,但是:
- 我在
normalizeAndSendErrorFromLibY
和normalizeAndSendErrorFromLibX
之间有冗余代码
- 在我的真实用例中,我必须链接超过 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);