Skipping promise chain after handling error

后端 未结 3 610
星月不相逢
星月不相逢 2021-01-02 11:06

Using the https://github.com/kriskowal/q library, I\'m wondering if it\'s possible to do something like this:

// Module A

function moduleA_exportedFunction(         


        
相关标签:
3条回答
  • 2021-01-02 11:37

    Let's talk about control constructs.

    In JavaScript, code flows in two ways when you call a function.

    • It can return a value to the caller, indicating that it completed successfully.
    • It can throw an error to the caller, indicating that an exceptional operation occurred.

    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:

    • It can return a promise or a value which indicates that it completed successfully.
    • It can throw an error which indicates that an exceptional state occured.

    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. It describes how control passes from one statement from another. You can consider .then an abstraction over a semicolon.

    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. 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:

    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:

    moduleA_exportedFunction - this function makes an API request and returns the service as a ServiceData object if the data is available. 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.

    some_bad_c_api() - this function foos a bar, on failure it terminates the process.

    So, what bothers us about terminating the process in that API anyway?

    It's all about responsibility.

    • It's the responsibility of the called API to convey whether the API request was successful.
    • 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. 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:

    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.

    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.

    0 讨论(0)
  • 2021-01-02 11:46

    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

    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;
        }
      });
    }
    
    0 讨论(0)
  • 2021-01-02 12:02

    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.

    So with promises, it would basically be something like this (disclaimer: this is psuedo-code-ish and untested)

    // 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! :)

    0 讨论(0)
提交回复
热议问题