Reputation: 24099
I chain a series of promises:
this.getData.then(this.getMoreData).then(this.getEvenMoreData);
At some point the user may decide to cancel the request and request something else.
How can I cancel the propagation of the chain?
Upvotes: 9
Views: 8230
Reputation: 5251
Fun little challenge!
Without knowing exactly which task, request or process you're launching AND how the user can interrupt the said process, it's difficult to recommend any solution to "break" a .then(...)
chain without doing some hack / trick that will trigger the .catch(...)
rejection callback.
That being said, see if this example sheds some light at all.
Pay particular attention to the makeInterruptablePromise function and how it's used:
var bar = $('.progress-bar');
var h3 = $("h3");
var isEscape;
function log(msg, replace) {
h3[replace ? 'html' : 'append'](msg + "<br/>");
}
$(document).keydown(e => {
switch(e.keyCode) {
case 27: //ESCAPE
return isEscape = true;
case 32: //SPACE
return runDemo();
}
});
function makeInterruptablePromise(cbStatus) {
return new Promise((resolve, reject) => {
function loop() {
switch(cbStatus()) {
case 1: return resolve();
case -1: return reject();
default: requestAnimationFrame(loop);
}
}
//Don't forget to start the loop!
loop();
})
}
function runDemo() {
log("Wait for it... (ESC to interrupt, SPACE to replay)", true);
isEscape = false;
var timeToComplete = 2000;
var timeStart = Date.now();
function updateBar() {
var timeDiff = Date.now() - timeStart;
var timePercent = timeDiff / timeToComplete;
TweenMax.set(bar, {scaleX: 1 - timePercent});
return timePercent > 1;
}
makeInterruptablePromise(() => {
if(isEscape) return -1;
if(updateBar()) return 1;
return 0;
})
.then(() => log("Inside *then* chain."))
.catch(() => log("Skipped *then* chain!"))
}
runDemo(); //Run first time.
body {
background-color: #123456;
color: #fff;
}
.progress-bar {
display: block;
width: 200px;
height: 10px;
background-color: #88f;
transform-origin: top left;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.2/TweenMax.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="progress-bar"></div>
<h3></h3>
What this essentially boils down to, is I'm passing a callback to makeInterruptablePromise
to "monitor" for 3 possible statuses.
requestAnimationFrame(...)
method. setTimeout(...)
calibrated to trigger per screen
refreshes).Now, to affect how these statuses changes over time, I demonstrated this by using ESCAPE
as the interrupt status (-1) and a timer that runs for 2 seconds. Once complete, the timer returns status (1).
Not sure it can fit your need, but could be useful for anyone else trying to break Promises via some external / asynchronous factor.
Upvotes: 0
Reputation: 55792
You'd have to check for the state (of whether you should cancel or not) inside each chained method:
var userRequestedCancel = false;
this
.getData()
.then(function() {
if(userRequestedCancel) {
return Promise.reject('user cancelled');
}
return getMoreData();
})
.then(function() {
if(userRequestedCancel) {
return Promise.reject('user cancelled');
}
return getEvenMoreData();
})
Or perhaps a slightly more elegant way (edited to pass context and arguments to callback
methods)
var currentReq = false;
var userRequestedCancel = false;
var shouldContinue = function(cb,args) {
if(userRequestedCancel) {
return Promise.reject('user cancelled');
}
currentReq = cb.apply(this,args);
return currentReq;
}
var onCancel = function() {
userRequestedCancel = true;
currentReq && currentReq.abort();
}
this
.getData()
.then(function() {
return shouldContinue(getMoreData,arguments);
})
.then(function() {
return shouldContinue(getEvenMoreData,arguments);
})
If you need to cancel the current request as well, that is kind of trivial, set your current ajax
request to be a global variable, and whatever event sets the userRequestedCancel
flag to true, have that also cancel the ajax
request (see edited code above)
Upvotes: 8
Reputation: 3382
In order to cancel a promise chain you need to throw an error. Just have a look at the code below
function CancelError() {
this.message = 'Cancelled';
}
obj
.then(function() {
throw new CancelError();
})
.catch(function(err) {
if (err instanceof CancelError) {
// Promise got cancelled
}
throw err; // throw the other mistakes
});
Upvotes: 3