Stop running processes after a Promise is rejected

前端 未结 2 765
刺人心
刺人心 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: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');
      })

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

    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.')
          }
        );

提交回复
热议问题