处理错误后跳过承诺链

mjo*_*ble 8 javascript node.js promise q

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

基本上,如果服务结果不好,我想使用特定于模块A内部的逻辑来处理模块A中的故障,但仍然跳过promise链中剩余的模块B函数.

跳过模块B函数的显而易见的解决方案是从模块A抛出错误/原因.但是,我需要在模块B中处理它.理想情况下,我想在模块B中不需要任何额外的代码来执行它.那.

这可能是不可能的:)或者反对Q的一些设计原则.

在这种情况下,您会建议什么样的替代方案?

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

  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
      }
    })
    
    Run Code Online (Sandbox Code Playgroud)
  2. 在模块A中执行自定义错误处理,然后在处理错误后,抛出假的拒绝原因.在模块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
      }
    })
    
    Run Code Online (Sandbox Code Playgroud)

解决方案1需要将模块A特定代码添加到模块B.

解决方案2解决了这个问题,但整个假拒绝方法看起来非常苛刻.

你能推荐其他解决方案吗?

Ben*_*aum 16

我们来谈谈控制结构.

在JavaScript中,当您调用函数时,代码以两种方式流动.

  • 它可以return是调用者的值,表示它已成功完成.
  • 它可能throw会给调用者带来错误,表明发生了异常操作.

它看起来像:

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

Promises模拟这种完全相同的行为.

在Promises中,当您在.then处理程序中调用函数时,代码以两种方式流动:

  • 它可以return是一个表示成功完成的承诺或值.
  • 它可能throw是一个错误,表明发生了异常状态.

它看起来像:

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

承诺控制本身的模型流程

承诺是对概念排序操作本身的抽象.它描述了控件如何从一个语句传递到另一个语句.您可以考虑.then使用分号进行抽象.

我们来谈谈同步代码

让我们看看同步代码在您的案例中的外观.

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

那么,如何继续我们的其余代码就是这么简单returning.这在同步代码和带有promise的异步代码中是相同的.执行非常具体的错误处理也没问题.

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

doA();
doB();
doC(); // make doD never execute and not throw an exception
doD();
Run Code Online (Sandbox Code Playgroud)

好吧,就算不是马上,还有一个相当简单的方法,使国防部从来没有引起DOC进入一个无限循环执行:

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

因此,可能永远不会解决承诺 - 就像其他答案所暗示的那样 - 返回未决承诺.然而,这是非常差的流量控制,因为意图很难传达给消费者,并且可能很难调试.想象一下以下API:

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

有点混乱,不是吗:)?但是,它实际上存在于某些地方.在真正的旧API中找到以下内容并不罕见.

some_bad_c_api() - 这个函数会出现一个条形,失败时会终止进程.

那么,对于那个终止API的过程有什么困扰呢?

这完全是责任.

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

在你的情况下.ModelA只是违反了其责任的限制,它无权对程序的流程做出这样的决定.消费它的人应该做出这些决定.

更好的解决方案是抛出错误并让消费者处理它.我将使用蓝鸟承诺在这里,因为他们不仅是两个数量级速度更快,并有一个更现代的API -他们也有很多很多更好的调试设施-在这种情况下-糖条件渔获物和更好的堆栈跟踪:

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

所以在一行中 - 你会做你在同步代码中会做的事情,就像promises的情况一样.

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


mjo*_*ble 1

受到 Benjamin Gruenbaum 的评论和回答的启发 - 如果我用同步代码编写此代码,我将使moduleA_exportedFunctionreturn为shouldContinue布尔值。

因此,有了承诺,它基本上会是这样的(免责声明:这是伪代码并且未经测试)

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

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