web-dev-qa-db-ja.com

Promiseが拒否された後、プロセスの実行を停止します

私は正常に機能する次のコードを使用していますが、問題は、エラーが発生したときに、他のすべての約束を停止させたいということです。たとえば、chi.getCommand(val1, val2)が拒否を送信し、例外キャッチに到達した場合、_chss.exe_とapp.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!が出力されるため、resolveが呼び出されます!

PDATE1

GetStatusを停止するのは、process.exit(1)を配置したときだけですが、これによりすべてのプロセスが強制終了されます。Imcatchブロックに到達した場合)に備えて、関数getCommandのすべてのチェーンを停止したいだけです。

  1. 方法はありますか?
  2. blueBirdのバグですか?私は「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);
    });
_

私が得たエラーは

{[エラー:spawn cmdr ENOENT]コード: 'ENOENT'、errno: 'ENOENT'、syscall: 'spawn cmdbug'、パス: 'cmdr'、spawnargs:['/ g'、 'npm install express --save'] } {[エラー:spawn cmdbug ENOENT]コード: 'ENOENT'、errno: 'ENOENT'、syscall: '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( "ポートを待機中" +ポート+ "試行:" +再試行);

PDATE2変更しようとしたとき@arturが2番目のオプションで示唆しているように、私は回復呼び出しでエラーが発生しました。エラーは次のとおりです。

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

       }
    });
  });
}
_
20
user4209821

@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>

更新

再帰的に定義されたアクション(再試行など)をキャンセルできます。このような場合の最善の戦略は、アクション自体を再帰的な動作で混乱させないことです。以下のスニペットでは、私のポイントを説明する非常に単純なラッパーを作成しました。

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でもないことです。これは、一般的には確かに 受け入れられない です。または.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>
7
artur grzesiak

実際のコード(UPDATE1のコード)では、getCommandgetStatusと同時に実行しており、順番に実行しているわけではありません。両方を呼び出しています(開始しています)before子プロセスは失敗し、失敗しても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);
});
2
Bergi