【问题标题】:Stop running processes after a Promise is rejected在 Promise 被拒绝后停止运行进程
【发布时间】:2016-05-18 15:38:46
【问题描述】:

我正在使用以下代码,它工作正常,但问题是当我收到错误时,我希望它停止所有其他承诺。例如,如果 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! 被打印,所以解析被调用!

更新1

停止 getStatus 的唯一方法是当我输入 process.exit(1) 但这会杀死所有进程,我只想停止函数 getCommand 的所有链,以防我 到达 catch 块强>,

  1. 有什么办法吗?
  2. 是蓝鸟的错误吗?我使用“蓝鸟”:“2.9.34”

函数getCommand(方法,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);
    });

我得到的错误是

{ [错误:spawn 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 是引发错误的函数!

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

查询状态代码为:

// 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("等待端口" + 端口 + " 尝试:" + 重试);

更新2 当尝试更改第二个选项中的@artur 建议时,我在递归调用中遇到错误,错误是:

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

       }
    });
  });
}

【问题讨论】:

  • Promise.all 已经这样做了。 bluebirdjs.com/docs/api/promise.all.html
  • @Mark:我很确定不是。也许它是从其他地方调用的?
  • @Bergi - 请查看我的代码更新
  • @Bergi - 所以在这种情况下,我看到了console.log,所以承诺被拒绝(后行),我看到 getStatus 仍然有效,你建议做什么来克服这个?
  • 你确定在拒绝承诺之前没有履行/解决吗?

标签: javascript node.js promise bluebird


【解决方案1】:

正如@Esailija 指出的那样,bluebird 内置了取消机制——这对于简单的异步计算来说确实非常好,而且肯定完全没问题

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>

更新

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

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)
}
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.1/bluebird.js"&gt;&lt;/script&gt;

原始答案

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

基于承诺的基本解决方案

我将问题提炼到最低限度,并使其可在浏览器中运行。 以下方法的缺点是,在取消后,promise 将永远不会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();
})

基于可观察的解决方案

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

  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.')
      }
    );
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.7/rx.all.js"&gt;&lt;/script&gt;

【讨论】:

  • Bluebird 已取消开箱即用
  • @Esailija 哦,很酷 - 不知道。所以更容易 - 将更新我的答案。顺便提一句。感谢这个非常酷的项目。
  • 这本书的作者错了,他的任何论点都被证明是错误的,但他仍然继续写那些废话:/
  • @Esailija for me 你在bluebird 库中创建的方法非常有意义。现在 - 由于缺乏原生取消机制 - 我们处于无法中止“获取”请求的情况,这本身就是完全疯狂的。
  • 可取消承诺的规范适用于github.com/domenic/cancelable-promise
【解决方案2】:

好吧,在您的实际代码(来自 UPDATE1 的代码)中,您同时运行 getCommandgetStatus,而不是按顺序运行。您正在调用(启动)他们两个之前子进程失败,当它失败时,没有什么可以阻止getStatus

就像在你的第一个 sn-p 中一样将它们链接在一起,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);
});

【讨论】:

  • 如果它发出任何事件,我可以拒绝/解决,然后将其放在承诺链上并使用 then...但这里不是这种情况
  • 你可以在上面加上.timeout()
  • ok 1+,能否请您添加带有超时的 update1 示例,如果您认为这是我最好的做法,我将关闭此帖子,谢谢先生!
  • 不确定系统上的死机时间可以从 1 到 15 秒...,也许还有其他方法,例如,如果我收到错误,从 catch 中发出它然后停止 getStatus,怎么办您认为?但不知道该怎么做..我的意思是保持代码原样(update1)没有超时
  • 您可以将getCommand() 承诺作为参数传递给getStatus(),并在is rejected 时停止递归
猜你喜欢
  • 1970-01-01
  • 2019-11-22
  • 2018-08-17
  • 2020-01-12
  • 2016-03-30
  • 2016-12-03
  • 2016-03-27
  • 2021-10-23
  • 2011-02-21
相关资源
最近更新 更多