首页 文章

Promise被拒绝后停止运行进程

提问于
浏览
20

我正在使用以下可正常工作的代码,但问题是当我收到错误时,我希望它能阻止所有其他的承诺 . 例如,如果 chi.getCommand(val1, val2) ,将发送拒绝,我得到异常捕获,我想取消 chss.exeapp.getStatus(12); 的承诺我怎样才能实现?

var start = Promise.all([
      chi.getCommand(val1, val2),
      chi.findAndUpdateCustomer()
    ]).spread(function (command, customer) {
        return chss.exe(runnableDoc, command, customer)
                 .delay(10)
                 .then(function (val) {
                   if (val) console.log(val);
                   return app.getStatus(12);
                 });
    }).catch(function (err) {
        // catch and handle errors and when it come to here I want it to stops all the chain above
    });

这是get命令的代码:

function getCommand(method, cmd) {
  return new Promise(function (resolve, reject) {
    ...
    child.stderr.on('data', function (data) {
        console.log('stderr: here!' + data);
        reject(data);
    });
}

打印控制台日志 stderr: here! ,以便调用解析!

UPDATE1

唯一阻止getStatus的是当我把 process.exit(1) 但是这个杀了所有进程,我只是想停止函数getCommand的所有链,以防Im arriving to the catch block

有办法吗?它是blueBird中的错误吗?我用“蓝鸟”:“2.9.34”

function getCommand(method,cmd){return new Promise(function(resolve,reject){

var spawn = require('child_process').spawn;
var ls = spawn("cmdbug",["/c","npm install express --save"]);


    ls.on('error', function (err) {
        console.log(err);
        reject(err);
    });

我得到的错误是

{[错误:生成cmdr ENOENT]代码:'ENOENT',错误:'ENOENT',系统调用:'spawn cmdbug',路径:'cmdr',spawnargs:['/ g','npm install express --save' ] {{错误:生成cmdbug ENOENT]代码:'ENOENT',错误:'ENOENT',系统调用:'spawn cmdbug',路径:'cmdr',spawnargs:['/ g','npm install express --save ']}子进程失败,代码为-4058

仍然是getStatus的进程正在写入控制台 .

我使用而不是用于测试的代码是:

getCommand 是抛出 error! 的函数

var start= function () {
    return new Promise.all([
        childP.getChildProcessCommand(val1, val2),
        childP.findAndUpdateCustomer()
    ]).spread(function (cmd, updated) {
            //Execute child process
            return Promise.all([
                childP.getCommand('spawn', cmd),
                app.getStatus(51000,10,1);
            ]).catch(function (err) {
                // catch and handle errors
                console.log("An error occur: " + err);
                return;
            })
        }).catch(function (err) {
            // catch and handle errors
            console.log("An error occur: " + err);
            return;
        })
}();

The code for check status is:

// Returns a promise that resolves when the port is open

checkPortStatus: function(port, host){
  return new Promise((resolve, reject) => {
    portscanner.checkPortStatus(port, host, function(error, status) {
      if(error)
        reject(error);
      else if(status === 'open')
        resolve(status);
      else
        reject(new Error('Port is not open'));
    });
  });
},

// THE API function
getStatus: function(port, retriesLeft) {

  const TIME_BETWEEN_CHECKS = 1000;
  const HOST = '127.0.0.1';
  const RETRIES = 20;
  retriesLeft = retriesLeft === void 0 ? RETRIES : retriesLeft;

  if(!port) throw new Error('Port is required');
  if(retriesLeft === 0) Promise.reject('Timed Out');

  return new Promise((resolve, reject) => {

    // If it rejects, we do added work.
    this.checkPortStatus(port, host).then(resolve, error => {
     console.log("Waiting for port " + port + " attempt: " + retry);
      setTimeout(() => {

        this.getStatus(port, retriesLeft - 1).then(resolve, reject);

      }, TIME_BETWEEN_CHECKS);
    });
  });
}

我在控制台中看到错误,仍然可以看到以下控制台日志的10次尝试 . console.log(“等待端口”端口“尝试:”重试);

UPDATE2 当尝试更改As @artur建议在第二个选项中我在recoursive调用中出错时错误是:

TypeError:无法读取未定义的属性'then'

这就是我尝试过的:

getStatus: function(port, retriesLeft) {

  const TIME_BETWEEN_CHECKS = 1000;
  const HOST = '127.0.0.1';
  const RETRIES = 20;
  retriesLeft = retriesLeft === void 0 ? RETRIES : retriesLeft;

  if(!port) throw new Error('Port is required');
  if(retriesLeft === 0) Promise.reject('Timed Out');

  var promise = new Promise((resolve, reject) => {

    // If it rejects, we do added work.
    this.checkPortStatus(port, host).then(resolve, error => {
     console.log("Waiting for port " + port + " attempt: " + retry);
      setTimeout(() => {
        //The error in the following recursive call
        this.getStatus(port, retriesLeft - 1).then(resolve, reject);

      }, TIME_BETWEEN_CHECKS);
      }).catch(function (error) {
         return reject(error);
     });
        return {
            promise:promise,
    cancel: function() {
        console.log('cancelling');
        clearTimeout(token);
        }

       }
    });
  });
}

2 回答

  • 2

    正如@Esailija所指出的那样 bluebird 内置了取消机制 - 这对于简单的异步计算非常好并且肯定是 totally fine .

    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>
    

    更新

    您可以取消递归定义的操作(例如重试) . 在这种情况下,最好的策略不是通过递归行为来破坏动作本身 . 在下面的代码片段中,我创建了一个非常简单的包装器,它说明了我的观点 .

    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>
    

    原始答案

    一般来说承诺很好,但他们不提供开箱即用的取消机制 . 在某些情况下(例如https://github.com/whatwg/fetch/issues/27)这是非常有问题的,在您的情况下,取消选项也非常方便 . 唯一有效的选择是自己添加它 .

    基于承诺的基本解决方案

    我将问题提炼到最低限度并使浏览器可以运行 . 以下方法的缺点是取消后,承诺永远不会 resolve 也不会 reject - 这在一般情况下肯定是unacceptable . 或者 .cancel 可以用某些特殊符号拒绝承诺 . 这些方法似乎都不优雅 .

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

    基于可观察的解决方案

    对于基本的操作顺序,承诺是好的,但有一种方法可用更优越的方法:即可观察的 . 它们不仅提供内置的取消/处理机制,而且允许处理多个发出的值,并在非常严格的控制下保持复杂的异步执行 .

    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>
    
  • 6

    好吧,在您的实际代码(UPDATE1中的代码)中,您同时运行 getCommandgetStatus ,而不是按顺序运行 . 你在子进程失败之前调用(启动)它们,并且当它发生时,没有什么可以阻止 getStatus .

    只需将它们链接在一起就像在你的第一个片段中那样, getCommand 中的拒绝将导致 getStatus 根本不运行 . 您可以使用

    childP.getCommand('spawn', cmd)
    .timeout(5000)
    .then(function(cmdresult) {
        return app.getStatus(51000, 10, 1);
    }).catch(function (err) {
        console.log("An error occured: " + err);
    });
    

相关问题