Order of promises in AngularJS
Asked Answered
R

4

5

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 come 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 incoming 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?

Roundshouldered answered 8/7, 2016 at 11:11 Comment(4)
Not sure what your using for an API but in my .Net apis I've used SignalR to handle scenarios like this.Aymara
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?Cloots
Possible duplicate of Promise - is it possible to force cancel a promiseStephi
Here is a duplicate of being able to cancel an $http request #13928557Pes
K
5

I do it by generating a requestId, and in the promise's then() function I check if the response is coming 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:

  1. generate the requestId based on the URL + parameters.
  2. 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
  3. When the request finishes, remove the requestId's entry from the hash-table.

Knobkerrie answered 30/7, 2016 at 17:58 Comment(1)
Thanks a lot! I will accept this answer since it actually refers to angularJS which is what i asked for. The answer of Redu contains a working example for this technique (without using Angular)Roundshouldered
N
4

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));
Nevermore answered 29/7, 2016 at 8:21 Comment(0)
M
0

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
    });
};
Marduk answered 31/7, 2016 at 2:6 Comment(2)
I found this in another thread after i posted this question. This still only works for $http calls though.Roundshouldered
Ah I see, so you are looking for something with $resource.Marduk
S
0

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).

Shuler answered 1/8, 2016 at 4:33 Comment(1)
this might have some other very useful applications but with this method i get "random" data (depending on the times the promises need to resolve) instead of the most up-to-date. So it does not answer the question unless i am missing something.Roundshouldered

© 2022 - 2024 — McMap. All rights reserved.