2015-08-16 140 views
27

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

try { 
    someFunction(); 
} catch (err) { 
    if (!(err instanceof MyCustomError)) 
    return -1; 
} 
someOtherFunction(); 

从本质上说,我测试捕获错误,如果它不是我期望我会返回给调用者的错误,否则程序继续执行。然而,这种逻辑不会与无极工作:

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改为一个block,我仍然无法打破一系列链接的Promise,因为从then/catch块返回的任何内容都将成为沿着链传播的Promise。

我不知道Promise能否实现这个逻辑;如果不是,为什么?我非常奇怪,一个Promise链永远不会被打破。谢谢!

2015年8月16日编辑: 根据到目前为止给出的答案,当时块返回的拒绝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是否不支持这种方法与它的catch/catch语句?

+2

为什么不''返回Promise.reject()'? – elclanrs

+0

从当前块返回被拒绝的承诺将使得当时的块返回被拒绝的承诺,该承诺通过承诺链传播直到它被捕获。 – lixiang

+0

根据您的澄清更新了我的答案。 – rrowland

回答

45

这不能用语言的特征来实现。但是,基于模式的解决方案是可用的。

以下是两种解决方案。

以前重新抛出错误

这种模式基本上是健康的...

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返回结果,那么链将前进到下一个功能N。
  • 如果errorHandler抛出一个MyCustomError,那么它将被重复地重新抛出链并被不符合if(error instanceof MyCustomError)协议的第一个错误处理程序(例如最终的.catch())捕获。
  • 如果errorHandler抛出任何其他类型的错误,那么链将进行到下一个捕获。

如果您需要根据抛出的错误的类型跳过链接结束的灵活性,此模式将非常有用。我期望的情况很少。

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

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

现在,

  • 如果设置ErrorHandler返回结果,那么链将前进到下一行。
  • 如果errorHandler抛出任何喜欢的东西,那么链将直接进入finalErrorHandler。

如果您希望总是跳到链尾,而不管抛出的错误类型如何,请使用此模式。自定义错误构造函数不是必需的,错误处理程序不需要以特殊方式写入。

DEMO

使用场合

哪种模式选择将已定的因素决定,但也可能由项目团队的性质。

  • 一人团队 - 你写一切,理解问题 - 如果你可以自由选择,然后运行你的个人喜好。
  • 多人团队 - 一人编写主链,其他人编写函数及其错误处理程序 - 如果可以的话,选择绝缘捕获 - 一切都在主链的控制下,不需要强制执行以某种方式编写错误处理程序的原则。
+0

谢谢你这个全面的答案!我已将你的回答标记为答案。但是,根据我对这个问题的进一步了解,我认为试图从catch块返回或跳过Promise链不应该被滥用。 Promise链中的“then”块表示整个过程的一个步骤。如果我们想把它作为引发独特错误的个别陈述来对待,我们最好不要使用Promise链 – lixiang

+1

没有滥用。像上面这些模式对于异步流量控制来说非常普遍且非常必要。 –

+0

很好的回答!但我认为你的第一个小提琴在执行myCustomError时遇到了一个问题,就是它使得它的行为与任何其他错误一样(看到它只是取消注释'reject(new Error(...')),所以我认为第5行应该是: 'myCustomError.prototype = Error;' –

4

没有内置的功能来跳过您请求的剩余链的全部。但是,你可以通过每个抓抛出一定的误差模仿这种行为:

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块之前,任何其他错误都将被控制台记录下来。

9

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

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

注意您的两个功能后,我已经删除()所以你只是路过的功能参考,不立即调用该函数。这将允许承诺基础设施决定是否在将来调用承诺。如果你犯了这个错误,它会完全抛弃你的承诺,因为事情会被调用。


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

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

你可以看到在this jsFiddle几个例子,它显示了三种情况:

  1. 从废品处理程序返回常规值,将导致未来.then()决心处理程序被调用(例如:正常处理继续),

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

  3. 没有一个拒绝处理当前导致正常解析处理,停止所有决心处理被跳过,直到你得到一个拒绝处理程序或链的末端。

+0

谢谢你的收获,我改变了!你能看看我的扩展问题吗? – lixiang

+0

@lixiang - 你不能使用'return'来停止承诺中的所有进一步处理。你不能因为他们的异步性质,所以实际上没有一个确切的类比。我已经概述了可用于承诺的一般选项。如果所有下游的'.catch()'处理程序检查错误类型并重新抛出它,如果它不是他们特别处理的东西,那么你可以抛出一个别人不会处理的唯一错误,并且它会停止所有进一步的处理。它将遍历所有下游的'.catch()'处理程序,但每个处理程序都会重新生成以最终到达链的末尾。 – jfriend00

+0

@lixiang - 你甚至可以创建自己的'abort'类错误(Error类的一个子类)的规则,即不需要重新抛出中间流'.catch()'处理程序。但是,承诺并没有内置这样的东西。 – jfriend00