处理错误后跳过承诺链

使用https://github.com/kriskowal/q库,我想知道是否有可能做这样的事情:

// Module A function moduleA_exportedFunction() { return promiseReturningService().then(function(serviceResults) { if (serviceResults.areGood) { // We can continue with the rest of the promise chain } else { performVerySpecificErrorHandling(); // We want to skip the rest of the promise chain } }); } // Module B moduleA_exportedFunction() .then(moduleB_function) .then(moduleB_anotherFunction) .fail(function(reason) { // Handle the reason in a general way which is ok for module B functions }) .done() ; 

基本上如果服务结果不好,我想处理模块A中的故障,使用特定于模块A内部的逻辑,但仍然跳过承诺链中剩余的模块Bfunction。

跳过模块B函数的明显的解决scheme是从模块A抛出一个错误/原因。然后,我将需要在模块B中处理。理想情况下,我想这样做,而不需要在模块B中的任何额外的代码那。

这可能是不可能的:)或者反对Q的一些devise原则。

在这种情况下,你会build议什么样的替代scheme?

我有两种方法,但都有缺点:

  1. 抛出模块A的特定错误,并将特定的处理代码添加到模块B:

     .fail(function(reason) { if (reason is specificError) { performVerySpecificErrorHandling(); } else { // Handle the reason in a general way which is ok for module B functions } }) 
  2. 在模块A中执行自定义error handling,然后处理错误后,抛出一个假拒绝原因。 在模块B中添加一个条件来忽略这个假的原因:

     .fail(function(reason) { if (reason is fakeReason) { // Skip handling } else { // Handle the reason in a general way which is ok for module B functions } }) 

解决scheme1需要将模块A特定的代码添加到模块B.

解决scheme2解决了这个问题,但是整个假拒绝方法似乎非常黑客。

你能推荐其他解决scheme吗?

我们来谈谈控制结构。

在JavaScript中,调用函数时,代码以两种方式stream动。

  • 它可以return一个值给调用者,表明它已经成功完成。
  • 它可以向调用者throw一个错误,表明发生了exception的操作。

它看起来像这样:

 function doSomething(){ // every function ever if(somethingBad) throw new Error("Error operating"); return value; // successful completion. } try{ doSomething(); console.log("Success"); } catch (e){ console.log("Boo"); } 

承诺模型完全相同的行为。

在Promises中,当你在一个.then处理程序中调用一个函数时,代码将以两种方式stream动:

  • 它可以return一个表示成功完成的承诺或值。
  • 它可以throw一个错误,表示出现exception状态。

它看起来像这样:

 var doSomething = Promise.method(function(){ if(somethingBad) throw new Error("Error operating"); return someEventualValue(); // a direct value works here too }); // See note, in Q you'd return Q.reject() Promise.try(function(){ // in Q that's Q().then doSomething(); console.log("Success"); }).catch(function(e){ console.log("Boo"); }); 

承诺控制自身的模型stream程

承诺是对概念sorting操作本身的抽象。 它描述了控制如何从另一个语句传递。 你可以考虑一个分号的抽象。

我们来谈谈同步代码

让我们来看看同步代码在你的情况下会是怎样的。

 function moduleA_exportedFunction() { var serviceResults = someSynchronousFunction(); if (serviceResults.areGood) { // We can continue with the rest of our code } else { performVerySpecificErrorHandling(); // We want to skip the rest of the chain } } 

那么,如何继续我们的代码的其余部分只是returning 。 这在同步代码和带有promise的asynchronous代码中是一样的。 执行非常具体的error handling也是可以的。

我们如何跳过同步版本中的其余代码?

 doA(); doB(); doC(); // make doD never execute and not throw an exception doD(); 

那么,即使不是立即,也一个相当简单的方法来使doD永远不会通过使doC进入无限循环来执行:

 function doC() { if (!results.areGood) { while(true){} // an infinite loop is the synchronous analogy of not continuing // a promise chain. } } 

所以, 可能永远不会解决一个承诺 – 就像其他答案所表明的那样 – 返回一个未决的承诺。 然而,这是非常差的stream量控制,因为意图很难传达给消费者,并且很可能很难debugging。 想象一下下面的API:

moduleA_exportedFunction – 此函数发出一个API请求,并在数据可用时将服务作为ServiceData对象返回。 否则,它将进入无限循环的程序

有点混乱,是不是:)? 但是,它确实存在于一些地方。 在真正旧的API中find以下内容并不罕见。

some_bad_c_api() – 这个函数是一个栏,当它终止进程时失败。

那么,什么使我们终止API的过程困扰呢?

这完全是责任。

  • 被调用的API负责传达API请求是否成功。
  • 来电者有责任决定在每种情况下做什么。

在你的情况。 ModelA只是简单地违反了责任限制,它不应该有权就程序的stream程作出这样的决定。 谁消费它应该做出这些决定。

更好的解决scheme是抛出一个错误,让消费者处理它。 我将在这里使用蓝鸟承诺 ,因为它们不仅快两个数量级,而且有更现代化的API – 它们也有更好的debugging工具 – 在这种情况下 – 糖条件捕获和更好的堆栈跟踪:

 moduleA_exportedFunction().then(function(result){ // this will only be reached if no error occured return someOtherApiCall(); }).then(function(result2){ // this will be called if the above function returned a value that is not a // rejected promise, you can keep processing here }).catch(ApiError,function(e){ // an error that is instanceof ApiError will reach here, you can handler all API // errors from the above `then`s in here. Subclass errors }).catch(NetworkError,function(e){ // here, let's handle network errors and not `ApiError`s, since we want to handle // those differently }).then(function(){ // here we recovered, code that went into an ApiError or NetworkError (assuming // those catch handlers did not throw) will reach this point. // Other errors will _still_ not run, we recovered successfully }).then(function(){ throw new Error(); // unless we explicitly add a `.catch` with no type or with // an `Error` type, no code in this chain will run anyway. }); 

所以在一行中 – 你会做同样的代码,你会做什么,通常是承诺的情况。

注意Promise.method只是一个Bluebird用于包装函数的便利函数,我只是讨厌同步抛出承诺返回的API,因为它会造成重大的破坏。

这是一种devise的东西。 通常,当模块或服务返回一个承诺时,如果调用成功,您希望它解决,否则失败。 即使你知道这个电话不成功,承诺也不会解决或失败,但基本上是一个沉默的失败。

但是,嘿,我不知道你的模块的具体细节或者原因,所以如果你想在这种情况下默默地失败,你可以通过返回一个未解决的承诺来做到这一点:

//模块A

 function moduleA_exportedFunction() { return promiseReturningService().then(function(serviceResults) { if (serviceResults.areGood) { // We can continue with the rest of the promise chain } else { performVerySpecificErrorHandling(); // We want to skip the rest of the promise chain return q.defer().promise; } }); } 

受到Benjamin Gruenbaum的评论和回答的启发 – 如果我在同步代码中写这个,我会让moduleA_exportedFunction返回一个shouldContinue布尔值。

所以承诺,基本上是这样的(免责声明:这是伪代码,ish和未经testing)

 // Module A function moduleA_exportedFunction() { return promiseReturningService().then(function(serviceResults) { if (serviceResults.areGood) { // We can continue with the rest of the promise chain return true; } else { performVerySpecificErrorHandling(); // We want to skip the rest of the promise chain return false; } }); } // Module B moduleA_exportedFunction() .then(function(shouldContinue) { if (shouldContinue) { return moduleB_promiseReturningFunction().then(moduleB_anotherFunction); } }) .fail(function(reason) { // Handle the reason in a general way which is ok for module B functions // (And anything unhandled from module A would still get caught here) }) .done() ; 

它确实需要模块B中的一些处理代码,但逻辑既不是特定于模块A的内部,也不涉及抛出和忽略假错误 – 任务完成! 🙂