im1dermike im1dermike - 4 months ago 7
AngularJS Question

Prevent previous, yet longer-running $http request from returning after most recent request

I just came across a weird situation and wasn't able to find an answer for after some searching.

I have a textbox that I'm using to allow a user to type keywords to filter table data. I have an

ng-change
directive on the input element which will fire this code:

return $http.get(url).then(function (response) {
return response.data;
});


All of this has worked great, until our tester just found some undesirable behavior in IE11. Here is an example of typing "M-A-T-T" into the textbox in IE10:

enter image description here

As you can see, each subsequent request takes longer than the first so the result of the fourth and final request is the one the controller receives.

Here is the same example of typing "M-A-T-T" into the textbox in IE11.

enter image description here

Inexplicably, the second request takes almost 2 seconds to complete which is after the fourth and final request completed. This results in the results from the "MA" request displaying when the user is expecting the results of the "MATT" request (which is what is currently in their textbox).

How can this be dealt with in Angular? Thanks in advance.

UPDATE
Based on frosty's response, I've implemented the following (in addition to bouncing) which works great:

var cancelDeferred;

var getUsers = function (criteria) {
if (cancelDeferred) {
cancelDeferred.resolve();
}
cancelDeferred = $q.defer();
return $http.get(url, { timeout: cancelDeferred.promise }).then(function (response) {
cancelDeferred = undefined;
return response.data;
});
};


The main challenge, actually, was handling errors when this method is called. The timeout returns an error just like a 500 would return an error. I want to ignore the timeouts and handle the actual errors. Here is my solution for that:

function onError(data, status) {
if (data.data !== null && data.status !== 0) {
//handle error
}
}


Now I'll try to figure out if there is a way to implement this promise-timing-out globally instead of having to alter a ton of $http.get() calls...

Answer

In the documentation for $http, it talks about a config object that you can pass as a second argument to the $http.get call, like this:

$http.get(url, config);

In that config, it talks about a "timeout" property that you can set. If you set that property as a Promise, then if you resolve the promise, it will abort the request. Check out the documentation for the description there.

Even if you use a debounce, you will want to use this "timeout" to abort the request.

https://docs.angularjs.org/api/ng/service/$http

So you would do something like this:

var cancelDeferred;
function makeRequest(){
    if(cancelDeferred) cancelDeferred.resolve(); //when this gets resolved, the previous request will abort. 
    cancelDeferred = $q.defer();
    return $http.get(url,{timeout:cancelDeferred.promise})
        .then(function(res){
            cancelDeferred = undefined;
            return res;
        });
}

So, you pass a promise to the .get request, inside the config object as the "timeout" property. If you resolve this deferred before the response comes back, it will abort the request.

Comments