Question:
Is there an "easy" way to cancel ($q
-/$http
-)promises in AngularJS or determine the order in which promises were resolved?
Example
I have a long running calculation and i request the result via $http
. Some actions or events require me to restart the calculation (and thus sending a new $http
request) before the initial promise is resolved. Thus i imagine i can't use a simple implementation like
$http.post().then(function(){
//apply data to view
})
because I can't ensure that the responses e back in the order in which i did send the requests - after all i want to show the result of the latest calculation when all promises were resolved properly.
However I would like to avoid waiting for the first response until i send a new request like this:
const timeExpensiveCalculation = function(){
return $http.post().then(function(response){
if (isNewCalculationChained) {return timeExpensiveCalculation();}
else {return response.data;}
})
}
Thoughts:
When using $http
i can access the config-object on the response to use some timestamps or other identifiers to manually order the ining responses. However i was hoping I could just tell angular somehow to cancel an outdated promise and thus not run the .then() function when it gets resolved.
This does not work without manual implementation for $q
-promises instead of $http
though.
Maybe just rejecting the promise right away is the way to go? But in both cases it might take forever until finally a promise is resolved before the next request is generated (which leads to an empty view in the meantime).
Is there some angular API-Function that i am missing or are there robust design patterns or "tricks" with promise chaining or $q.all to handle multiple promises that return the "same" data?
Question:
Is there an "easy" way to cancel ($q
-/$http
-)promises in AngularJS or determine the order in which promises were resolved?
Example
I have a long running calculation and i request the result via $http
. Some actions or events require me to restart the calculation (and thus sending a new $http
request) before the initial promise is resolved. Thus i imagine i can't use a simple implementation like
$http.post().then(function(){
//apply data to view
})
because I can't ensure that the responses e back in the order in which i did send the requests - after all i want to show the result of the latest calculation when all promises were resolved properly.
However I would like to avoid waiting for the first response until i send a new request like this:
const timeExpensiveCalculation = function(){
return $http.post().then(function(response){
if (isNewCalculationChained) {return timeExpensiveCalculation();}
else {return response.data;}
})
}
Thoughts:
When using $http
i can access the config-object on the response to use some timestamps or other identifiers to manually order the ining responses. However i was hoping I could just tell angular somehow to cancel an outdated promise and thus not run the .then() function when it gets resolved.
This does not work without manual implementation for $q
-promises instead of $http
though.
Maybe just rejecting the promise right away is the way to go? But in both cases it might take forever until finally a promise is resolved before the next request is generated (which leads to an empty view in the meantime).
Is there some angular API-Function that i am missing or are there robust design patterns or "tricks" with promise chaining or $q.all to handle multiple promises that return the "same" data?
Share Improve this question edited Jul 13, 2016 at 12:51 H W asked Jul 8, 2016 at 11:11 H WH W 2,5963 gold badges24 silver badges45 bronze badges 4- Not sure what your using for an API but in my .Net apis I've used SignalR to handle scenarios like this. – jbrown Commented Jul 8, 2016 at 12:34
- 1 Great question, looking forward to answers. But wouldn't it be a lot easier to handle the logic within the .then instead of trying to avoid the promise from being resolved? – user2263572 Commented Jul 8, 2016 at 14:01
- Possible duplicate of Promise - is it possible to force cancel a promise – Roamer-1888 Commented Jul 9, 2016 at 8:44
- Here is a duplicate of being able to cancel an $http request stackoverflow./questions/13928057/… – jjbskir Commented Jul 28, 2016 at 22:13
4 Answers
Reset to default 5 +50I do it by generating a requestId
, and in the promise's then()
function I check if the response is ing from the most recent requestId
.
While this approach does not actually cancel the previous promises, it does provide a quick and easy way to ensure that you are handling the most recent request's response.
Something like:
var activeRequest;
function doRequest(params){
// requestId is the id for the request being made in this function call
var requestId = angular.toJson(params); // I usually md5 hash this
// activeRequest will always be the last requestId sent out
activeRequest = requestId;
$http.get('/api/something', {data: params})
.then(function(res){
if(activeRequest == requestId){
// this is the response for last request
// activeRequest is now handled, so clear it out
activeRequest = undefined;
}
else {
// response from previous request (typically gets ignored)
}
});
}
Edit:
On a side-note, I wanted to add that this concept of tracking requestId's
can also be applied to preventing duplicate requests. For example, in my Data
service's load(module, id)
method, I do a little process like this:
- generate the
requestId
based on the URL + parameters. check in requests hash-table for the
requestId
- if
requestId
is not found: generate new request and store promise in hash-table - if
requestId
is found: simply return the promise from the hash-table
- if
When the request finishes, remove the
requestId
's entry from the hash-table.
Cancelling a promise is just making it not invoke the onFulfilled
and onRejected
functions at the then
stage. So as @user2263572 mentioned it's always best to let go the promise not cancelled (ES6 native promises can not be cancelled anyways) and handle this condition within it's then
stage (like disregarding the task if a global variable is set to 2 as shown in the following snippet) and i am sure you can find tons of other ways to do it. One example could be;
Sorry that i use v
(looks like check character) for resolve
and x
(obvious) for reject
functions.
var prom1 = new Promise((v,x) => setTimeout(v.bind(null,"You shall not read this"),2000)),
prom2,
validPromise = 1;
prom1.then(val => validPromise === 1 && console.log(val));
// oh what have i done..!?! Now i have to fire a new promise
prom2 = new Promise((v,x) => setTimeout(v.bind(null,"This is what you will see"),3000));
validPromise = 2;
prom2.then(val => validPromise === 2 && console.log(val));
I'm still trying to figure out a good way to unit test this, but you could try out this kind of strategy:
var canceller = $q.defer();
service.sendCalculationRequest = function () {
canceller.resolve();
return $http({
method: 'GET',
url: '/do-calculation',
timeout: canceller.promise
});
};
In ECMA6 promises, there is a Promise.race(promiseArray)
method. This takes an array of promises as its argument, and returns a single promise. The first promise to resolve in the array will hand off its resolved value to the .then
of the returned promise, while the other array promises that came in second, etc., will not be waited upon.
Example:
var httpCall1 = $http.get('/api/something', {data: params})
.then(function(val) {
return {
id: "httpCall1"
val: val
}
})
var httpCall2 = $http.get('/api/something-else', {data: params})
.then(function(val) {
return {
id: "httpCall2"
val: val
}
})
// Might want to make a reusable function out of the above two, if you use this in Production
Promise.race([httpCall1, httpCall2])
.then(function(winningPromise) {
console.log('And the winner is ' + winningPromise.id);
doSomethingWith(winningPromise.val);
});
You could either use this with a Promise polyfil, or look into the q.race that someone's developed for Angular (though I haven't tested it).