首页 文章

如何从Promise的catch / then块返回

提问于
浏览
34

有许多关于如何在使用JavaScript Promise进行编程时使用“then”和“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块,我仍然无法打破一系列链式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);
}

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

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

3 回答

  • 4

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

    这是两个解决方案 .

    Rethrow previous error

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

    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 .

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

    DEMO

    Insulated Catches

    另一种解决方案是引入一种机制来保持每个 .catch(errorHandlerN) "insulated",使其仅捕获由其相应的 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 .

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

    DEMO

    Usage cases

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

    • 一人团队 - 您可以编写所有内容并了解问题 - 如果您可以自由选择,那么请根据您的个人喜好进行操作 .

    • 多人团队 - 一个人写主链,其他人编写功能及其错误处理程序 - 如果可以,选择绝缘捕获 - 一切都在主链的控制之下,你不需要强制执行纪律以某种方式编写错误处理程序 .

  • 54

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

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

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

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

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


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

    • 如果没有人 grab 拒绝,它会立即停止承诺链,原始拒绝成为承诺的最终状态 . 不会调用后续处理程序 .

    • 如果捕获了promise拒绝并且没有返回任何内容或者拒绝处理程序返回任何正常值,则拒绝被视为已处理并且promise链继续并且后续处理程序被调用 . 无论你从拒绝处理程序返回什么,都会成为promise的当前值,就好像拒绝从未发生过一样(除了没有调用此级别的解析处理程序 - 而是调用拒绝处理程序) .

    • 如果捕获了promise拒绝并且您从拒绝处理程序中抛出错误或者您返回被拒绝的promise,则会跳过所有解析处理程序,直到链中的下一个拒绝处理程序 . 如果没有拒绝处理程序,则承诺链将停止,新发生的错误将成为承诺的最终状态 .


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

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

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

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

  • 9

    当您请求时,没有内置功能可以跳过整个剩余链 . 但是,您可以通过在每个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);
    }
    

    如果任何catch块被捕获 criticalError ,它们将直接跳到最后并抛出错误 . 在继续下一个 .then 块之前,任何其他错误都将是控制台记录的 .

相关问题