如何从 Promise 的 catch/then 块中返回? [英] How to return from a Promise's catch/then block?

查看:29
本文介绍了如何从 Promise 的 catch/then 块中返回?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有很多关于在使用 JavaScript Promise 编程时如何使用then"和catch"的教程.然而,所有这些教程似乎都忽略了一个重要的点:从 then/catch 块返回以打破 Promise 链.让我们从一些同步代码开始来说明这个问题:

尝试{someFunction();} 抓住(错误){if (!(err instanceof MyCustomError))返回-1;}someOtherFunction();

本质上,我正在测试一个捕获的错误,如果它不是我期望的错误,我将返回给调用者,否则程序将继续.但是,此逻辑不适用于 Promise:

Promise.resolve(someFunction).then(function() {console.log('someFunction 应该抛出错误');返回-2;}).catch(函数(错误){if (err instanceof MyCustomError) {返回-1;}}).then(someOtherFunction);

这个逻辑用于我的一些单元测试,我希望函数以某种方式失败.即使我将 catch 更改为 then 块,我仍然无法破坏一系列链接的 Promise,因为从 then/catch 块返回的任何内容都将成为沿链传播的 Promise.

不知Promise能否实现这个逻辑;如果不是,为什么?Promise 链永远不会被破坏,这对我来说很奇怪.谢谢!

2015 年 8 月 16 日根据目前给出的答案,then 块返回的被拒绝 Promise 将通过 Promise 链传播并跳过所有后续 then 块,直到被捕获(处理).这种行为很好理解,因为它只是模仿了以下同步代码(方法 1):

尝试{函数 1();函数 2();函数 3();函数 4();} 抓住(错误){//假设这个 err 是在 Function1 中抛出的;Function2、Function3 和 Function4 不会被执行控制台日志(错误);}

然而,我问的是同步代码中的以下场景(方法 2):

尝试{函数 1();} 抓住(错误){控制台日志(错误);//函数1的错误返回-1;//立即返回}尝试 {函数 2();} 抓住(错误){控制台日志(错误);}尝试 {函数 3();} 抓住(错误){控制台日志(错误);}尝试 {函数 4();} 抓住(错误){控制台日志(错误);}

我想以不同的方式处理不同功能中出现的错误.如方法 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) {//<<<<<<<测试先前抛出的错误抛出错误;} 别的 {//然后做 errorHandler1 的东西//返回结果或//抛出新的 MyCustomError() 或//抛出 new Error()、new RangeError() 等或一些其他类型的自定义错误.}}

现在:

  • 如果 errorHandler 返回结果,则链将前进到下一个 FunctionN.
  • 如果一个 errorHandler 抛出了一个 MyCustomError,那么它将被重复地向下抛出并被第一个不符合 if(error instanceof MyCustomError) 协议的错误处理程序捕获(例如一个 final.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.

如果您想始终跳到链的末尾,而不管抛出的错误类型如何,请使用此模式.不需要自定义错误构造函数,也不需要以特殊方式编写错误处理程序.

演示

用例

选择哪种模式取决于已经给出的考虑因素,但也可能取决于您的项目团队的性质.

  • 单人团队 - 您编写所有内容并了解问题 - 如果您可以自由选择,那么请根据您的个人喜好进行操作.
  • 多人团队 - 一个人编写主链,其他人编写函数及其错误处理程序 - 如果可以,请选择 Insulated Catches - 一切都在主链的控制之下,您无需强制执行以某种方式编写错误处理程序的纪律.

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:

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.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.

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!

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

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

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?

解决方案

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.

... 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.

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.

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.

DEMO

Insulated Catches

Another solution is to introduce a mechanism to keep each .catch(errorHandlerN) "insulated" such that it will catch only errors arising from its corresponding FunctionN, not from any preceding errors.

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.

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.

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.

DEMO

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.

这篇关于如何从 Promise 的 catch/then 块中返回?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆