问题
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