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