Stop running processes after a Promise is rejected

前端 未结 2 766
刺人心
刺人心 2021-02-07 02:34

I\'m using the following code which working OK, but the problem is that when I get an error, I want it to stops all the other promises. For example if chi.getCommand(val1,

相关标签:
2条回答
  • 2021-02-07 03:04

    Well, in your actual code (the one from UPDATE1) you are running getCommand concurrently to getStatus, not in sequence. You're calling (starting) both of them before the child process fails, and when it does there is nothing that would stop getStatus.

    Just chain them together like in your first snippet, where a rejection in getCommand will cause getStatus to not run at all. You can use

    childP.getCommand('spawn', cmd)
    .timeout(5000)
    .then(function(cmdresult) {
        return app.getStatus(51000, 10, 1);
    }).catch(function (err) {
        console.log("An error occured: " + err);
    });
    
    0 讨论(0)
  • 2021-02-07 03:09

    As @Esailija pointed out bluebird has cancellation mechanism built-in - which is really nice and for sure totally fine for simple async computations.

    Promise.config({
      cancellation: true
    });
    
    function createCancellableMock(result, time) {
    
      return new Promise(function(resolve, reject, onCancel) {
    
        // var child = runCommand();
        var token = setTimeout(function() {
          if (result) {
            console.log('almost done', result);
            resolve(result);
          } else {
            reject('_ERR_');
          }
        }, time);
    
        onCancel(function() {
          console.log('cancelling');
          // child.kill('SIGTERM');
          clearTimeout(token);
        })
      })
    
    }
    
    var op1 = createCancellableMock('ok-1', 1000);
    //var op2 = createCancellableMock('ok-2', 500);
    var op2 = createCancellableMock(null, 500); // will be rejected
    
    Promise.all([op1, op2])
      .spread(function(v1, v2) {
        console.log('BOTH-OK', v1, v2)
      })
      .catch(function() {
        console.error('ERROR');
        op1.cancel();
      })
      .finally(function() {
        console.log('finally');
      })
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.0/bluebird.core.js"></script>

    UPDATE

    You can cancel recursively defined actions (such as retries). The best strategy in such a case is not to mangle the action itself with the recursive behavior. In the below snippet I created a very simple wrapper which illustrates my point.

    var TOO_MANY_RETRIES_ERROR = 'too_many_retries_error';
    var PROB_OF_FAIL = 0.8;
    var INTERVAL = 200;
    var RETRIES = 5;
    
    var CANCEL_AFTER = null;
    //var CANCEL_AFTER = INTERVAL * (RETRIES/2);
    
    Promise.config({
      cancellation: true
    });
    
    function retryWithCancel(params) {
    
      // params = {op - operation to retry (it should return a promise, which either ),
      // interval - between retries, retries - number of retries }
    
      console.log('running, retries left ', params.retries);
    
      params = Object.assign({}, params); // copy params - no side-effects please
      params.retries--;
      if (params.retries <= 0) {
        console.error('too many retries');
        return Promise.reject(new Error(TOO_MANY_RETRIES_ERROR));
      }
    
      return new Promise(function(resolve, reject, onCancel) {
    
        var o = params.op()
          .catch(function() {
            return Promise.delay(params.interval)
              .then(retryWithCancel.bind(null, params))
              .catch(reject)
          })
          .then(resolve)
    
    
        onCancel(function() {
          console.log('Cancelling, retries left: ', params.retries);
          o.cancel();
        });
    
      })
    
    }
    
    function fakeOperation() {
    
      return Promise.delay(100)
        .then(function() {
          if (Math.random() > PROB_OF_FAIL) {
            return Promise.resolve('SUCCESS');
          } else {
            return Promise.reject(new Error('ERROR'));
          }
    
        })
    }
    
    var p = retryWithCancel({
        op: fakeOperation,
        interval: INTERVAL,
        retries: RETRIES
      })
      .then(console.log.bind(console))
      .catch(console.error.bind(console))
      .finally(console.log.bind(console, 'done'))
    
    if (CANCEL_AFTER) {
      setTimeout(function() {
        p.cancel();
      }, CANCEL_AFTER)
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.1/bluebird.js"></script>

    ORIGINAL ANSWER

    In general promises are great but they do not offer cancellation mechanism out of the box. It is pretty problematic in some scenarios (e.g. https://github.com/whatwg/fetch/issues/27) and in your case option to cancel would be pretty handy as well. The only valid option is to add it yourself.

    basic promise based solution

    I distilled the problem to bare minimum and made it browser runnable. The downside of the below approach is that after cancellation the promise will never resolve nor reject - which in general case is surely unacceptable. Alternatively .cancel may reject the promise with some special symbol. Neither of these approaches seem elegant.

    function createCancellableMock(result, time) {
        
        // child = null;
        var token = null ;
        var p = new Promise(function(resolve, reject) {
            
            // child = runCommand();
            token = setTimeout(function() {
                if (result) {
                    console.log('almost done', result);
                    resolve(result);
                } 
                else {
                    reject('_ERR_');
                }
            }, time);
        }
        )
        
        return {
            promise: p,
            cancel: function() {
                console.log('cancelling');
                // child.kill('SIGTERM');
                clearTimeout(token);
            }
        }
    }
    
    var op1 = createCancellableMock('ok-1', 1000);
    // var op2 = createCancellableMock('ok-2', 500);
    var op2 = createCancellableMock(null, 500); // will be rejected
    
    Promise.all([op1.promise, op2.promise])
    .then(function(vs) { // no spread in native implemantation
        console.log('BOTH-OK', vs[0], vs[1])
    })
    .catch(function() {
        console.error('ERROR');
        op1.cancel();
    })

    observable based solution

    For basic sequence of operations promises are fine, but there is a way more superior approach available: namely observables. Not only they offer built-in cancellation / disposing mechanism, but allow to deal with multiple values emitted and keep sophisticated async execution under very strict control.

      function createCancellableMock(result, time) {
    
        return Rx.Observable.create(function(observer) {
    
          var done = false;
          var token = setTimeout(function() {
            if (result) {
              console.log('almost done: ' + result);
              observer.onNext(result);
              observer.onCompleted();
            } else {
              observer.onError('_ERR_');
            }
          }, time);
    
          // this will be called upon `disposed`
          return function() {
            console.log('disposing, done: ', done);
            if (!done) {
              clearTimeout(token);
            }
          }
    
        })
    
      }
    
      var op1 = createCancellableMock('ok-1', 1000);
      //var op2 = createCancellableMock('ok-2', 500);
      var op2 = createCancellableMock(null, 500); // will be rejected
    
      op1.zip(op2)
        .catch(function(err) {
          // it was disposed automatically :) hurray
          console.log('Caught', err);
          // return Rx.Observable.empty(); // swallowing
          return Rx.Observable.throw(err); // throwing
    
        })
        .subscribe(function(vs) {
            console.log('BOTH-OK', vs[0], vs[1])
          },
          function(err) {
            console.error('Unhandled error', err);
          },
          function() {
            console.log('Upon successful termination.')
          }
        );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.7/rx.all.js"></script>

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