处理错误后跳过承诺链
使用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?
我有两种方法,但都有缺点:
-
抛出模块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 } })
-
在模块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的内部,也不涉及抛出和忽略假错误 – 任务完成! 🙂