Javascript 如何从 Promise 的 catch/then 块中返回?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/32032588/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to return from a Promise's catch/then block?
提问by lixiang
There are many tutorials on how to use "then" and "catch" while programming with JavaScript Promise. However, all these tutorials seem to miss an important point: returning from a then/catch block to break the Promise chain. Let's start with some synchronous code to illustrate this problem:
有很多关于在使用 JavaScript Promise 编程时如何使用“then”和“catch”的教程。然而,所有这些教程似乎都忽略了一个重要的点:从 then/catch 块返回以打破 Promise 链。让我们从一些同步代码开始来说明这个问题:
try {
someFunction();
} catch (err) {
if (!(err instanceof MyCustomError))
return -1;
}
someOtherFunction();
In essence, I am testing a caught error and if it's not the error I expect I will return to the caller otherwise the program continues. However, this logic will not work with Promise:
本质上,我正在测试一个捕获的错误,如果它不是我期望的错误,我将返回给调用者,否则程序将继续。但是,此逻辑不适用于 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);
This logic is used for some of my unit tests where I want a function to fail in a certain way. Even if I change the catch to a then block I am still not able to break a series of chained Promises because whatever is returned from the then/catch block will become a Promise that propagates along the chain.
这个逻辑用于我的一些单元测试,我希望函数以某种方式失败。即使我将 catch 更改为 then 块,我仍然无法破坏一系列链接的 Promise,因为从 then/catch 块返回的任何内容都将成为沿链传播的 Promise。
I wonder if Promise is able to achieve this logic; if not, why? It's very strange to me that a Promise chain can never be broken. Thanks!
不知道Promise是否能够实现这个逻辑;如果不是,为什么?Promise 链永远不会被破坏,这对我来说很奇怪。谢谢!
Edit on 08/16/2015: According to the answers given so far, a rejected Promise returned by the then block will propagate through the Promise chain and skip all subsequent then blocks until is is caught (handled). This behavior is well understood because it simply mimics the following synchronous code (approach 1):
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);
}
However, what I was asking is the following scenario in synchronous code (approach 2):
但是,我要问的是同步代码中的以下场景(方法 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);
}
I would like to deal with errors raised in different functions differently. It's possible that I catch all the errors in one catch block as illustrated in approach 1. But that way I have to make a big switch statement inside the catch block to differentiate different errors; moreover, if the errors thrown by different functions do not have a common switchable attribute I won't be able to use the switch statement at all; under such a situation, I have to use a separate try/catch block for each function call. Approach 2 sometimes is the only option. Does Promise not support this approach with its then/catch statement?
我想以不同的方式处理不同功能中引发的错误。如方法 1 所示,我有可能在一个 catch 块中捕获所有错误。但是那样我必须在 catch 块中创建一个大的 switch 语句来区分不同的错误;此外,如果不同函数抛出的错误没有共同的可切换属性,我将根本无法使用 switch 语句;在这种情况下,我必须为每个函数调用使用单独的 try/catch 块。方法 2 有时是唯一的选择。Promise 的 then/catch 语句是否不支持这种方法?
回答by Roamer-1888
This can't be achieved with features of the language. However, pattern-based solutions are available.
这不能通过语言的特性来实现。但是,可以使用基于模式的解决方案。
Here are two solutions.
这里有两个解决方案。
Rethrow previous error
重新抛出之前的错误
This pattern is basically sound ...
这种模式基本上是健全的......
Promise.resolve()
.then(Function1).catch(errorHandler1)
.then(Function2).catch(errorHandler2)
.then(Function3).catch(errorHandler3)
.then(Function4).catch(errorHandler4)
.catch(finalErrorHandler);
Promise.resolve()
is not strictly necessary but allows all the .then().catch()
lines to be of the same pattern, and the whole expression is easier on the eye.
Promise.resolve()
不是绝对必要的,但允许所有.then().catch()
线条具有相同的图案,并且整个表达在眼睛上更容易。
... but :
... 但 :
- if an errorHandler returns a result, then the chain will progress to the next line's success handler.
- if an errorHandler throws, then the chain will progress to the next line's error handler.
- 如果 errorHandler 返回结果,则链将前进到下一行的成功处理程序。
- 如果 errorHandler 抛出,则链将前进到下一行的错误处理程序。
The desired jump out of the chain won't happen unless the error handlers are written such that they can distinguish between a previously thrown error and a freshly thrown error. For example :
除非错误处理程序被编写为能够区分先前抛出的错误和新抛出的错误,否则不会发生期望的跳出链。例如 :
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.
}
}
Now :
现在 :
- if an errorHandler returns a result, then the chain will progress to the next FunctionN.
- if an errorHandler throws a MyCustomError, then it will be repeatedly rethrown down the chain and caught by the first error handler that does not conform to the
if(error instanceof MyCustomError)
protocol (eg a final .catch()). - if an errorHandler throws any other type of error, then the chain will progress to the next catch.
- 如果 errorHandler 返回结果,则链将前进到下一个 FunctionN。
- 如果一个 errorHandler 抛出一个 MyCustomError,那么它将被重复地重新抛出到链中并被第一个不符合
if(error instanceof MyCustomError)
协议的错误处理程序(例如最终的 .catch())捕获。 - 如果 errorHandler 抛出任何其他类型的错误,则链将进行到下一个捕获。
This pattern would be useful if you need the flexibility to skip to end of chain or not, depending on the type of error thrown. Rare circumstances I expect.
如果您需要灵活地跳到链尾或不跳到链尾,则此模式将非常有用,具体取决于抛出的错误类型。我期望的罕见情况。
Insulated Catches
绝缘渔获
Another solution is to introduce a mechanism to keep each .catch(errorHandlerN)
"insulated" such that it will catch only errors arising from itscorresponding FunctionN
, not from any preceding errors.
另一种解决方案是引入一种机制来保持每个.catch(errorHandlerN)
“绝缘”,这样它只会捕获由其相应的引起的错误,而FunctionN
不是任何先前的错误。
This can be achieved by having in the main chain only success handlers, each comprising an anonymous function containing a subchain.
这可以通过在主链中只有成功处理程序来实现,每个成功处理程序包含一个包含子链的匿名函数。
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);
Here Promise.resolve()
plays an important role. Without it, Function1().catch(errorHandler1)
would be in the main chain the catch()
would not be insulated from the main chain.
这里Promise.resolve()
起着重要的作用。没有它,Function1().catch(errorHandler1)
将在主链中,catch()
不会与主链绝缘。
Now,
现在,
- if an errorHandler returns a result, then the chain will progress to the next line.
- if an errorHandler throws anything it likes, then the chain will progress directly to the finalErrorHandler.
- 如果 errorHandler 返回结果,则链将前进到下一行。
- 如果 errorHandler 抛出任何它喜欢的东西,那么链将直接进入 finalErrorHandler。
Use this pattern if you want always to skip to the end of chain regardless of the type of error thrown. A custom error constructor is not required and the error handlers do not need to be written in a special way.
如果您想始终跳到链的末尾,而不管抛出的错误类型如何,请使用此模式。不需要自定义错误构造函数,并且不需要以特殊方式编写错误处理程序。
Usage cases
用例
Which pattern to choose will determined by the considerations already given but also possibly by the nature of your project team.
选择哪种模式取决于已经给出的考虑因素,但也可能取决于项目团队的性质。
- One-person team - you write everything and understand the issues - if you are free to choose, then run with your personal preference.
- Multi-person team - one person writes the master chain and various others write the functions and their error handlers - if you can, opt for Insulated Catches - with everything under control of the master chain, you don't need to enforce the discipline of writing the error handlers in that certain way.
- 一个人的团队——你写一切并理解问题——如果你可以自由选择,那么按照你的个人喜好运行。
- 多人团队——一个人编写主链,其他人编写函数及其错误处理程序——如果可以,选择 Insulated Catches——一切都在主链的控制之下,你不需要强制执行以某种方式编写错误处理程序。
回答by jfriend00
First off, I see a common mistake in this section of code that could be completely confusing you. This is your sample code block:
首先,我在这部分代码中看到了一个常见的错误,它可能会让您完全困惑。这是您的示例代码块:
Promise.resolve(someFunction()).then(function() {
console.log('someFunction should throw error');
return -2;
}).catch(function(err) {
if (err instanceof MyCustomError) {
return -1;
}
}).then(someOtherFunction());
You need pass function references to a .then()
handler, not actually call the function and pass their return result. So, this above code should probably be this:
您需要将函数引用传递给.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
Note that I've removed ()
after the functions in the .then()
handler so you are just passing the function reference, not immediately calling the function. This will allow the promise infrastructure to decide whether to call the promise in the future or not. If you were making this mistake, it will totally throw you off for how the promises are working because things will get called regardless.
请注意,我()
在.then()
处理程序中的函数之后删除了,因此您只是传递函数引用,而不是立即调用函数。这将允许承诺基础设施决定是否在未来调用承诺。如果你犯了这个错误,它会让你完全放弃承诺的工作方式,因为无论如何都会被调用。
Three simple rules about catching rejections.
关于捕捉拒绝的三个简单规则。
- If nobody catches the rejection, it stops the promise chain immediately and the original rejection becomes the final state of the promise. No subsequent handlers are invoked.
- If the promise rejection is caught and either nothing is returned or any normal value is returned from the reject handler, then the reject is considered handled and the promise chain continues and subsequent handlers are invoked. Whatever you return from the reject handler becomes the current value of the promise and it as if the reject never happened (except this level of resolve handler was not called - the reject handler was called instead).
- If the promise reject is caught and you either throw an error from the reject handler or you return a rejected promise, then all resolve handlers are skipped until the next reject handler in the chain. If there are no reject handlers, then the promise chain is stopped and the newly minted error becomes the final state of the promise.
- 如果没有人发现拒绝,它会立即停止承诺链,原始拒绝成为承诺的最终状态。不调用后续处理程序。
- 如果承诺拒绝被捕获并且没有返回任何内容或从拒绝处理程序返回任何正常值,则拒绝被视为已处理并且承诺链继续并调用后续处理程序。无论您从拒绝处理程序返回什么,都将成为承诺的当前值,并且就好像拒绝从未发生过一样(除了没有调用此级别的解析处理程序 - 而是调用了拒绝处理程序)。
- 如果承诺拒绝被捕获,并且您从拒绝处理程序中抛出错误或返回被拒绝的承诺,则所有解析处理程序都将被跳过,直到链中的下一个拒绝处理程序。如果没有拒绝处理程序,那么承诺链将停止,新产生的错误成为承诺的最终状态。
You can see a couple examples in this jsFiddlewhere it shows three situations:
您可以在此 jsFiddle中看到几个示例,其中显示了三种情况:
Returning a regular value from a reject handler, causes the next
.then()
resolve handler to be called (e.g. normal processing continues),Throwing in a reject handler causes normal resolve processing to stop and all resolve handlers are skipped until you get to a reject handler or the end of the chain. This is effective way to stop the chain if an unexpected error is found in a resolve handler (which I think is your question).
Not having a reject handler present causes normal resolve processing to stop and all resolve handlers are skipped until you get to a reject handler or the end of the chain.
从拒绝处理程序返回一个常规值,导致
.then()
调用下一个解析处理程序(例如正常处理继续),抛出拒绝处理程序会导致正常的解析处理停止,并且所有解析处理程序都将被跳过,直到到达拒绝处理程序或链的末尾。如果在解析处理程序中发现意外错误,这是停止链的有效方法(我认为这是您的问题)。
没有拒绝处理程序会导致正常的解析处理停止,并且所有解析处理程序都将被跳过,直到您到达拒绝处理程序或链的末尾。
回答by rrowland
There is no built-in functionality to skip the entirety of the remaining chain as you're requesting. However, you could imitate this behavior by throwing a certain error through each catch:
没有内置功能可以在您请求时跳过整个剩余链。但是,您可以通过在每个 catch 中抛出某个错误来模仿这种行为:
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);
}
If any of the catch blocks caught a criticalError
they would skip straight to the end and throw the error. Any other error would be console logged and before continuing to the next .then
block.
如果任何 catch 块捕获到 a,criticalError
它们将直接跳到最后并抛出错误。在继续下一个.then
块之前,任何其他错误都将被控制台记录。