如何从Promise的catch/then块返回

lix*_*ang 45 javascript asynchronous promise

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

try {
  someFunction();
} catch (err) {
  if (!(err instanceof MyCustomError))
    return -1;
}
someOtherFunction();
Run Code Online (Sandbox Code Playgroud)

本质上,我正在测试一个捕获的错误,如果不是错误,我希望我将返回调用者,否则程序继续.但是,这种逻辑不适用于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);
Run Code Online (Sandbox Code Playgroud)

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

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

编辑于2015年8月16日:根据到目前为止给出的答案,由then块返回的被拒绝的Promise将通过Promise链传播并跳过所有后续块,直到被捕获(处理).这种行为很好理解,因为它只是模仿以下同步代码(方法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);
}
Run Code Online (Sandbox Code Playgroud)

但是,我要问的是同步代码中的以下场景(方法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);
} 
Run Code Online (Sandbox Code Playgroud)

我想以不同的方式处理在不同函数中引发的错误.我可能会捕获一个catch块中的所有错误,如方法1中所示.但是这样我必须在catch块中创建一个大的switch语句来区分不同的错误; 而且,如果不同函数抛出的错误没有共同的可切换属性,我将无法使用switch语句; 在这种情况下,我必须为每个函数调用使用单独的try/catch块.方法2有时是唯一的选择.Promise不支持使用then/catch语句支持这种方法吗?

Roa*_*888 65

使用该语言的功能无法实现这一点.但是,可以使用基于模式的解决方案.

这是两个解决方案.

Rethrow上一个错误

这种模式基本上是合理的......

Promise.resolve()
.then(Function1).catch(errorHandler1)
.then(Function2).catch(errorHandler2)
.then(Function3).catch(errorHandler3)
.then(Function4).catch(errorHandler4)
.catch(finalErrorHandler);
Run Code Online (Sandbox Code Playgroud)

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.
    }
}
Run Code Online (Sandbox Code Playgroud)

现在:

  • 如果errorHandler返回结果,则链将进入下一个FunctionN.
  • 如果errorHandler抛出一个MyCustomError,那么它将在链中反复重新抛出并被第一个不符合if(error instanceof MyCustomError)协议的错误处理程序捕获(例如最终的.catch()).
  • 如果errorHandler抛出任何其他类型的错误,那么链将进入下一个catch.

如果您需要灵活地跳转到链的末尾,这种模式将非常有用,具体取决于抛出的错误类型.我期待的情况很少见.

DEMO

绝缘捕获

另一种解决方案是引入一种机制来保持每个.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);
Run Code Online (Sandbox Code Playgroud)

这里Promise.resolve()起着重要的作用.没有它,Function1().catch(errorHandler1)将在主链中catch()不会与主链隔离.

现在,

  • 如果errorHandler返回结果,则链将进入下一行.
  • 如果errorHandler抛出任何它喜欢的东西,那么链将直接进入finalErrorHandler.

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

DEMO

用例

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

  • 一人团队 - 您可以编写所有内容并了解问题 - 如果您可以自由选择,那么请根据您的个人喜好进行操作.
  • 多人团队 - 一个人写主链和其他各个人编写功能及其错误处理程序 - 如果可以,选择绝缘捕获 - 一切都在主链的控制之下,你不需要强制执行以某种方式编写错误处理程序.


jfr*_*d00 12

首先,我看到这部分代码中的一个常见错误可能让您感到困惑.这是您的示例代码块:

Promise.resolve(someFunction()).then(function() {
  console.log('someFunction should throw error');
  return -2;
}).catch(function(err) {
   if (err instanceof MyCustomError) {
     return -1;
   }
}).then(someOtherFunction());
Run Code Online (Sandbox Code Playgroud)

您需要传递函数引用,而不是实际调用函数并传递它们的返回结果.所以,上面的代码应该是这样的:

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
Run Code Online (Sandbox Code Playgroud)

请注意,我已经删除.then()了两个函数,因此您只是传递函数引用,而不是立即调用函数.这将允许promise基础结构决定是否在将来调用promise.如果你犯了这个错误,它将完全抛弃你的承诺是如何工作的,因为事情会被调用.


关于捕获拒绝的三个简单规则.

  1. 如果没有人抓住拒绝,它会立即停止承诺链,原始拒绝成为承诺的最终状态.不会调用后续处理程序.
  2. 如果捕获了promise拒绝并且没有返回任何内容或者拒绝处理程序返回任何正常值,则拒绝被认为是处理并且promise链继续并且后续处理程序被调用.无论你从拒绝处理程序返回什么,都会成为promise的当前值,就好像拒绝从未发生过一样(除了没有调用这个级别的解析处理程序 - 而是调用拒绝处理程序).
  3. 如果捕获了promise拒绝并且您从拒绝处理程序抛出错误或者您返回被拒绝的promise,则会跳过所有解析处理程序,直到链中的下一个拒绝处理程序.如果没有拒绝处理程序,则承诺链将停止,新的错误将成为承诺的最终状态.

您可以在此jsFiddle中看到几个示例,其中显示了三种情况:

  1. 从拒绝处理程序返回常规值会导致()调用下一个解析处理程序(例如,正常处理继续),

  2. 抛入拒绝处理程序会导致正常的解析处理停止,并且所有解析处理程序都会被跳过,直到您到达拒绝处理程序或链的末尾.如果在解析处理程序中发现意外错误(这是我的问题),这是停止链的有效方法.

  3. 没有拒绝处理程序会导致正常的解析处理停止,并且所有解析处理程序都会被跳过,直到您到达拒绝处理程序或链的末尾.