问题
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, val2)
, will send a reject and I got to the exception catch, I want to cancel the promises for chss.exe
and app.getStatus(12);
How can I 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 and handle errors and when it come to here I want it to stops all the chain above
});
This is the code of get 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! are printed so the resolve are called!
UPDATE1
The only thing which stops the getStatus is when I put the process.exit(1)
But this kill all the process, I just want to stop all the chain of the function getCommand in case Im arriving to the catch block,
- is there a way?
- is it bug in blueBird ? I 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 which I got is
{ [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 with code -4058
And still the process of getStatus is writing to the console.
The code which I use and not for testing is:
The getCommand is the function that throw the 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);
});
});
}
And I see the error in the console and still see the console log of the following for 10 attempts. console.log("Waiting for port " + port + " attempt: " + retry);
UPDATE2 When trying to change As @artur suggest in the second option I got error in the recoursive call the error is:
TypeError: Cannot read property 'then' of undefined
This is what 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 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);
}
}
});
});
}
回答1:
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');
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.0/bluebird.core.js"></script>
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)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.1/bluebird.js"></script>
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.')
}
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.7/rx.all.js"></script>
回答2:
Well, in your actual code (the one from UPDATE1) you are running getCommand
concurrently to getStatus
, not in sequence. You're calling (starting) both of them before the child process fails, and when it does there is nothing that would stop getStatus
.
Just chain them together like in your first snippet, where a rejection in getCommand
will cause getStatus
to not run at all. You can use
childP.getCommand('spawn', cmd)
.timeout(5000)
.then(function(cmdresult) {
return app.getStatus(51000, 10, 1);
}).catch(function (err) {
console.log("An error occured: " + err);
});
来源:https://stackoverflow.com/questions/35278729/stop-running-processes-after-a-promise-is-rejected