如何从Promise's catch/then block返回
How to return from a Promise's catch/then block?
在JavaScript Promise编程时,有很多关于如何使用"then"answers"catch"的教程。然而,所有这些教程似乎都忽略了重要的一点:从then/catch块返回以打破Promise链。让我们从一些同步代码开始来说明这个问题:
try {
someFunction();
} catch (err) {
if (!(err instanceof MyCustomError))
return -1;
}
someOtherFunction();
本质上,我正在测试捕获的错误,如果它不是我期望的错误,我将返回给调用者,否则程序继续。但是,这个逻辑不适用于Promise:
Promise.resolve(someFunction).then(function() {
console.log('someFunction should throw error');
return -2;
}).catch(function(err) {
if (err instanceof MyCustomError) {
return -1;
}
}).then(someOtherFunction);
此逻辑用于我的一些单元测试,其中我希望函数以某种方式失败。即使我将catch更改为then块,我仍然无法打破一系列链接的承诺,因为从then/catch块返回的任何内容都将成为沿链传播的承诺。
我想知道Promise是否能够实现这个逻辑;如果不是,为什么?对我来说很奇怪,承诺链永远不会被打破。谢谢!
2015年8月16日编辑:根据目前给出的答案,由then块返回的被拒绝的Promise将通过Promise链传播,并跳过所有后续的then块,直到它被捕获(处理)。这种行为很容易理解,因为它简单地模仿了以下同步代码(方法1):
try {
Function1();
Function2();
Function3();
Function4();
} catch (err) {
// Assuming this err is thrown in Function1; Function2, Function3 and Function4 will not be executed
console.log(err);
}
然而,我要问的是同步代码中的以下场景(方法2):
try {
Function1();
} catch(err) {
console.log(err); // Function1's error
return -1; // return immediately
}
try {
Function2();
} catch(err) {
console.log(err);
}
try {
Function3();
} catch(err) {
console.log(err);
}
try {
Function4();
} catch(err) {
console.log(err);
}
我想以不同的方式处理不同函数中产生的错误。正如方法1中所示,我可能在一个catch块中捕获所有错误。但是那样的话,我必须在catch块中写一个大的switch语句来区分不同的错误;此外,如果不同函数抛出的错误没有一个共同的可切换属性,我将根本无法使用switch语句;在这种情况下,我必须为每个函数调用使用单独的try/catch块。方法2有时是唯一的选择。Promise的then/catch语句不支持这种方法吗?
这是无法用语言的特性实现的。然而,基于模式的解决方案是可用的。
这里有两个解决方案。
重新抛出先前的错误
这个模式基本上是健全的…
Promise.resolve()
.then(Function1).catch(errorHandler1)
.then(Function2).catch(errorHandler2)
.then(Function3).catch(errorHandler3)
.then(Function4).catch(errorHandler4)
.catch(finalErrorHandler);
Promise.resolve()
不是严格必需的,但允许所有的.then().catch()
行都是相同的模式,整个表达式更容易看。
…但是:
- 如果errorHandler返回一个结果,那么链将继续到下一行的成功处理程序。
- 如果抛出errorHandler,那么该链将继续到下一行的错误处理程序。
function errorHandler1(error) {
if (error instanceof MyCustomError) { // <<<<<<< test for previously thrown error
throw error;
} else {
// do errorHandler1 stuff then
// return a result or
// throw new MyCustomError() or
// throw new Error(), new RangeError() etc. or some other type of custom error.
}
}
:
- 如果errorHandler返回一个结果,那么链将继续到下一个FunctionN。
- 如果errorHandler抛出MyCustomError,那么它将被重复地向下抛出并被第一个不符合
if(error instanceof MyCustomError)
协议的错误处理程序捕获(例如最终的.catch())。 - 如果errorHandler抛出任何其他类型的错误,那么该链将继续进行下一个捕获。
如果您需要根据抛出的错误类型灵活地跳转到链尾或不跳转到链尾,则此模式将非常有用。我想这种情况很少见。
绝缘了
另一个解决方案是引入一种机制来保持每个.catch(errorHandlerN)
"绝缘",这样它将只捕获由其对应的FunctionN
引起的错误,而不是从任何先前的错误。
这可以通过在主链中只使用成功处理程序来实现,每个成功处理程序由一个包含子链的匿名函数组成。
Promise.resolve()
.then(function() { return Function1().catch(errorHandler1); })
.then(function() { return Function2().catch(errorHandler2); })
.then(function() { return Function3().catch(errorHandler3); })
.then(function() { return Function4().catch(errorHandler4); })
.catch(finalErrorHandler);
这里Promise.resolve()
起着重要的作用。如果没有它,Function1().catch(errorHandler1)
将在主链中,catch()
将无法与主链隔离。
- 如果errorHandler返回一个结果,那么链将继续到下一行。
- 如果一个errorHandler抛出了它喜欢的任何东西,那么这个链将直接进展到finalErrorHandler。
如果您希望总是跳到链的末尾,而不管抛出的错误类型是什么,请使用此模式。不需要自定义错误构造函数,也不需要以特殊的方式编写错误处理程序。
选择哪种模式取决于已经给出的考虑,也可能取决于您的项目团队的性质。
- 一个人的团队-你写的一切,了解问题-如果你可以自由选择,然后运行你的个人偏好。
- 多人团队——一个人写主链,其他人写函数和它们的错误处理程序——如果可以的话,选择绝缘捕获——在主链的控制下,你不需要强制以某种方式编写错误处理程序。
首先,我在这段代码中发现了一个常见的错误,它可能会让您完全困惑。这是您的示例代码块:
Promise.resolve(someFunction()).then(function() {
console.log('someFunction should throw error');
return -2;
}).catch(function(err) {
if (err instanceof MyCustomError) {
return -1;
}
}).then(someOtherFunction()); // <== Issue here
您需要将函数引用传递给.then()
处理程序,而不是实际调用函数并传递其返回结果。所以,上面的代码应该是这样的:
Promise.resolve(someFunction()).then(function() {
console.log('someFunction should throw error');
return -2;
}).catch(function(err) {
if (err instanceof MyCustomError) {
// returning a normal value here will take care of the rejection
// and continue subsequent processing
return -1;
}
}).then(someOtherFunction); // just pass function reference here
请注意,我在.then()
处理程序中的函数之后删除了()
,因此您只是传递函数引用,而不是立即调用函数。这将允许承诺基础结构决定将来是否调用承诺。如果你犯了这个错误,它会让你完全忘记承诺是如何工作的,因为事情会被调用。
抓住拒绝的三个简单规则。
- 如果没有人捕捉到拒绝,它立即停止承诺链,原始拒绝成为承诺的最终状态。没有调用后续处理程序。
- 如果捕获到承诺拒绝,并且不返回任何内容或从拒绝处理程序返回任何正常值,则认为拒绝已处理,承诺链继续并调用后续处理程序。无论你从拒绝处理程序返回什么,都会成为承诺的当前值,就好像拒绝从未发生过一样(除非没有调用这一级的解析处理程序——而是调用了拒绝处理程序)。
- 如果承诺拒绝被捕获,你抛出一个错误的拒绝处理程序,或者你返回一个被拒绝的承诺,那么所有的解析处理程序都被跳过,直到链中的下一个拒绝处理程序。如果没有拒绝处理程序,承诺链将停止,新生成的错误将成为承诺的最终状态。
您可以在这个jsFiddle中看到几个示例,其中显示了三种情况:
从拒绝处理程序返回一个常规值,导致下一个
.then()
解析处理程序被调用(例如正常处理继续),抛出一个拒绝处理程序会导致正常的解析处理停止,所有的解析处理程序都被跳过,直到你到达一个拒绝处理程序或链的末端。如果在解析处理程序中发现意外错误(我认为这是您的问题),这是停止链的有效方法。
没有拒绝处理程序会导致正常的解析处理停止,所有的解析处理程序都被跳过,直到你到达拒绝处理程序或链的末端。
没有内置功能可以在请求时跳过剩余链的整个部分。但是,您可以通过在每次捕获中抛出特定的错误来模仿这种行为:
doSomething()
.then(func1).catch(handleError)
.then(func2).catch(handleError)
.then(func3).catch(handleError);
function handleError(reason) {
if (reason instanceof criticalError) {
throw reason;
}
console.info(reason);
}
如果任何catch块捕获到criticalError
,它们将直接跳到末尾并抛出错误。在继续下一个.then
块之前,任何其他错误都将被控制台记录。
如果您可以使用较新的async
await
,这是相当简单的实现:
async function myfunc() {
try {
return await anotherAsyncFunction();
} catch {
//do error handling
// can be async or not.
return errorObjct();
}
}
let alwaysGetAValue = await myfunc();
根据您使用的技术,您可能需要某种高级包装器函数来允许顶级await
。
- 节点Js:How to catch a“;没有这样的文件或目录“;读取线模块出错
- Javascript:If-then语句在函数中不起作用
- angularjs无法读取未定义的属性then
- errors with Javascript try catch
- 如何使用(this)访问Angular 2 http rxjs catch函数中的对象属性
- Promise函数在.then之后未运行函数化
- Javascript 承诺 .catch 仍然调用 final variable.then
- 为什么 JavaScript Promise 有时既不使用 .catch 也不使用 .then
- 在 AngularJS 中使用 then 和 catch 时出错
- 与茉莉花中 ES6 承诺的 then/catch 方法同步
- Factory - Then and Catch Promise Chaining in AngularJS
- Promise : then vs then + catch
- Jasmin Mock $http (then, catch)
- 如何从Promise's catch/then block返回
- 获取api - get json主体在then和catch块为单独的状态码
- 答应立刻处理.catch和.then
- javascript try catch vs if then else
- 如何测试' catch '和' then '与sinon js
- 有没有办法回报对.catch和.then的承诺
- 即使我在所有 then-able 中使用拒绝回调,我是否总是需要在最后需要 catch()