javascript - Stop running processes after a Promise is rejected -
i'm using following code working ok, problem when error, want stops other promises. example if chi.getcommand(val1, val2), send reject , got exception catch, want cancel promises chss.exe , app.getstatus(12); how can achieve that?
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 , handle errors , when come here want stops chain above }); this code of command in short:
function getcommand(method, cmd) { return new promise(function (resolve, reject) { ... child.stderr.on('data', function (data) { console.log('stderr: here!' + data); reject(data); }); } the console log stderr: here! printed resolve called!
update1
the thing stops getstatus when put process.exit(1) kill process, want stop chain of function getcommand in case im arriving catch block,
- is there way?
- is bug in bluebird ? use "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); }); the error got
{ [error: spawn cmdr enoent] code: 'enoent', errno: 'enoent', syscall: 'spawn cmdbug', path: 'cmdr', spawnargs: [ '/g', 'npm install express --save' ] } { [error: spawn cmdbug enoent] code: 'enoent', errno: 'enoent', syscall: 'spawn cmdbug', path: 'cmdr', spawnargs: [ '/g', 'npm install express --save' ] } child process failed code -4058
and still process of getstatus writing console.
the code use , not testing is:
the getcommand function throw 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 , handle errors console.log("an error occur: " + err); return; }) }).catch(function (err) { // catch , handle errors console.log("an error occur: " + err); return; }) }(); the code check status is:
// returns promise resolves when port 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 not open')); }); }); }, // 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 required'); if(retriesleft === 0) promise.reject('timed out'); return new promise((resolve, reject) => { // if rejects, added work. this.checkportstatus(port, host).then(resolve, error => { console.log("waiting port " + port + " attempt: " + retry); settimeout(() => { this.getstatus(port, retriesleft - 1).then(resolve, reject); }, time_between_checks); }); }); } and see error in console , still see console log of following 10 attempts. console.log("waiting port " + port + " attempt: " + retry);
update2 when trying change @artur suggest in second option got error in recoursive call error is:
typeerror: cannot read property 'then' of undefined
this i've tried:
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 required'); if(retriesleft === 0) promise.reject('timed out'); var promise = new promise((resolve, reject) => { // if rejects, added work. this.checkportstatus(port, host).then(resolve, error => { console.log("waiting port " + port + " attempt: " + retry); settimeout(() => { //the error in 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); } } }); }); }
as @esailija pointed out bluebird has cancellation mechanism built-in - nice , sure totally fine 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); // 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 retries). best strategy in such case not mangle action recursive behavior. in below snippet created simple wrapper illustrates 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 retry (it should return promise, 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 great not offer cancellation mechanism out of box. pretty problematic in scenarios (e.g. https://github.com/whatwg/fetch/issues/27) , in case option cancel pretty handy well. valid option add yourself.
basic promise based solution
i distilled problem bare minimum , made browser runnable. downside of below approach after cancellation promise never resolve nor reject - in general case surely unacceptable. alternatively .cancel may reject promise 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); // 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 fine, there way more superior approach available: namely observables. not offer built-in cancellation / disposing mechanism, allow deal multiple values emitted , keep sophisticated async execution under 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); // 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); // rejected op1.zip(op2) .catch(function(err) { // 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>
Comments
Post a Comment