简体   繁体   English

处理错误后跳过承诺链

[英]Skipping promise chain after handling error

Using the https://github.com/kriskowal/q library, I'm wondering if it's possible to do something like this: 使用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()
;

Basically if the service results are bad, I'd like to handle the failure in module A, using logic that is specific to the internals of module A, but still skip the remaining module B functions in the promise chain. 基本上,如果服务结果不佳,我想使用特定于模块A内部的逻辑来处理模块A中的故障,但仍然跳过承诺链中其余的模块B功能。

The obvious solution for skipping module B functions is to throw an error/reason from module A. However, then I would need to handle that in module B. And ideally I'd like to do it without needing any extra code in module B for that. 跳过模块B功能的显而易见的解决方案是从模块A引发错误/原因。但是,我将需要在模块B中处理该错误/原因。理想情况下,我希望在模块B中不需要任何额外的代码来完成此操作那。

Which may very well be impossible :) Or against some design principles of Q. 这很可能是不可能的:)或违反Q的某些设计原则。

In which case, what kind of alternatives would you suggest? 在这种情况下,您会建议什么样的替代方案?

I have two approaches in mind, but both have their disadvantages: 我有两种方法,但都有缺点:

  1. Throw a specific error from module A and add specific handling code to module B: 从模块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. Perform the custom error handling in module A, then after handling the error, throw a fake rejection reason. 在模块A中执行自定义错误处理,然后在处理错误之后,抛出伪造的拒绝原因。 In module B, add a condition to ignore the fake reason: 在模块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 } }) 

Solution 1 requires adding module A specific code to module B. 解决方案1需要向模块B添加模块A特定代码。

Solution 2 solves this, but the whole fake rejection approach seems very hackish. 解决方案2解决了这个问题,但是整个伪造拒绝方法似乎非常棘手。

Can you recommend other solutions? 您能推荐其他解决方案吗?

Let's talk about control constructs. 让我们谈谈控制构造。

In JavaScript, code flows in two ways when you call a function. 在JavaScript中,调用函数时,代码以两种方式流动。

  • It can return a value to the caller, indicating that it completed successfully. 它可以向调用方return一个值,指示它已成功完成。
  • It can throw an error to the caller, indicating that an exceptional operation occurred. 它可能会向调用者throw错误,表明发生了异常操作。

It looks something like: 看起来像:

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 model this exact same behavior. 诺言为这种完全相同的行为建模。

In Promises, code flows in exactly two ways when you call a function in a .then handler: 在Promises中,在.then处理程序中调用函数时,代码以正好两种方式流动:

  • It can return a promise or a value which indicates that it completed successfully. 它可以return一个承诺或一个值,表明它已成功完成。
  • It can throw an error which indicates that an exceptional state occured. 它可能throw错误,表明发生了异常状态。

It looks something like: 看起来像:

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

Promises model flow of control itself 承诺控制本身的模型流程

A promise is an abstraction over the notion sequencing operations itself. 一个promise是对概念排序操作本身的抽象。 It describes how control passes from one statement from another. 它描述了控制如何从一个语句传递到另一个语句。 You can consider .then an abstraction over a semicolon. 你可以考虑.then在一个分号的抽象。

Let's talk about synchronous code 让我们谈谈同步代码

Let's see how synchronous code would look in your case. 让我们看看您的情况下同步代码的外观。

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
    }
}

So, how continuing with the rest of our code is simply returning . 因此,如何继续执行我们其余的代码只是在returning This is the same in synchronous code and in asynchronous code with promises. 这在同步代码和带有承诺的异步代码中是相同的。 Performing very specific error handling is also ok. 执行非常具体的错误处理也是可以的。

How would we skip the rest of the code in the synchronous version? 我们如何跳过同步版本中的其余代码?

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

Well, even if not immediately, there is a rather simple way to make doD never execute by causing doC to enter into an infinite loop: 好吧,即使不是立即执行,也可以通过使doC进入无限循环来使doD永远不执行:

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

So, it is possible to never resolve a promise - like the other answer suggests - return a pending promise. 因此, 可能永远都无法解决承诺-就像其他答案所建议的-返回待处理的承诺。 However, that is extremely poor flow control since the intent is poorly conveyed to the consumer and it will be likely very hard to debug. 但是,这是非常差的流量控制,因为意图没有很好地传达给用户,并且很可能很难调试。 Imagine the following API: 想象一下以下API:

moduleA_exportedFunction - this function makes an API request and returns the service as a ServiceData object if the data is available. moduleA_exportedFunction-此函数发出API请求,并在数据可用时作为ServiceData对象返回服务。 Otherwise, it enters the program into an endless loop . 否则,它将使程序进入无限循环

A bit confusing, isn't it :)? 有点混乱,不是:)吗? However, it actually exists in some places. 但是,它实际上存在于某些地方。 It's not uncommon to find the following in really old APIs. 在真正古老的API中找到以下内容并不少见。

some_bad_c_api() - this function foos a bar, on failure it terminates the process . some_bad_c_api() -此函数欺骗酒吧,一旦失败,它将终止进程

So, what bothers us about terminating the process in that API anyway? 那么,到底要终止该API中的流程,是什么困扰我们呢?

It's all about responsibility. 这都是关于责任的。

  • It's the responsibility of the called API to convey whether the API request was successful. 被调用的API负责传达API请求是否成功。
  • It is the responsibility of the caller to decide what to do in each case. 呼叫者有责任决定每种情况下的处理方式。

In your case. 就你而言。 ModelA is simply breaching the limit of its responsibility, it should not be entitled to make such decisions about the flow of the program. ModelA只是在违反其职责范围,它无权对程序的流程做出此类决定。 Whoever consumes it should make these decisions. 食用它的人应该做出这些决定。

Throw

The better solution is to throw an error and let the consumer handle it. 更好的解决方案是抛出错误并让消费者处理错误。 I'll use Bluebird promises here since they're not only two orders of magnitude faster and have a much more modern API - they also have much much better debugging facilities - in this case - sugar for conditional catches and better stack traces: 我将使用蓝鸟承诺在这里,因为他们不仅是两个数量级速度更快,并有一个更现代的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.
});

So in a line - you would do what you would do in synchronous code, as is usually the case with promises. 因此,在一行中,您将执行同步代码中的操作,这与promise一样。

Note Promise.method is just a convenience function Bluebird has for wrapping functions, I just hate synchronous throwing in promise returning APIs as it creates major breakage. 注意Promise.method只是Bluebird用于包装函数的便捷功能,我只是讨厌同步返回Promise返回的API,因为它会造成重大破坏。

It is kind of a design thing. 这是一种设计的东西。 In general, when a module or service returns a promise, you want it to resolve if the call was successful, and to fail otherwise. 通常,当模块或服务返回承诺时,您希望它解析调用是否成功,否则失败。 Having the promise neither resolve or fail, even though you know the call was unsuccessful, is basically a silent failure. 即使您知道呼叫不成功,但诺言既没有解决也没有失败,基本上是一种无声的失败。

But hey, I don't know the specifics of your modules, or reasons, so if you do want to fail silently in this case, you can do it by returning an unresolved promise: 但是,我不知道您模块的具体细节或原因,因此,如果您确实想在这种情况下静默失败,则可以通过返回未解决的承诺来做到这一点:

// Module A //模块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;
    }
  });
}

Inspired by Benjamin Gruenbaum's comments and answer - if I was writing this in synchronous code, I would make moduleA_exportedFunction return a shouldContinue boolean. 受本杰明·格鲁恩鲍姆(Benjamin Gruenbaum)的评论和答案的启发-如果我以同步代码编写此代码,则将使moduleA_exportedFunction返回一个shouldContinue布尔值。

So with promises, it would basically be something like this (disclaimer: this is psuedo-code-ish and untested) 因此,有了promise,基本上就是这样(免责声明:这是伪代码式的,未经测试)

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

It does require some handling code in module B, but the logic is neither specific to module A's internals nor does it involve throwing and ignoring fake errors - mission accomplished! 它确实需要模块B中的一些处理代码,但是逻辑既不特定于模块A的内部,也不涉及抛出和忽略虚假错误-已完成任务! :) :)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM