Étant donné une demande Ajax dans AngularJS
$http.get("/backend/").success(callback);
quel est le moyen le plus efficace d'annuler cette demande si une autre demande est lancée (même backend, paramètres différents par exemple).
Cette fonctionnalité était ajoutée à la version 1.1.5 via un paramètre de délai d'attente:
var canceler = $q.defer();
$http.get('/someUrl', {timeout: canceler.promise}).success(successCallback);
// later...
canceler.resolve(); // Aborts the $http request if it isn't finished.
Annuler Angular $ http Ajax avec la propriété timeout ne fonctionne pas dans Angular 1.3.15. Pour ceux qui ne peuvent pas attendre que cela soit corrigé, je partage une solution jQuery Ajax intégrée dans Angular.
La solution implique deux services:
Voici le service PendingRequestsService:
(function (angular) {
'use strict';
var app = angular.module('app');
app.service('PendingRequestsService', ["$log", function ($log) {
var $this = this;
var pending = [];
$this.add = function (request) {
pending.Push(request);
};
$this.remove = function (request) {
pending = _.filter(pending, function (p) {
return p.url !== request;
});
};
$this.cancelAll = function () {
angular.forEach(pending, function (p) {
p.xhr.abort();
p.deferred.reject();
});
pending.length = 0;
};
}]);})(window.angular);
Le service HttpService:
(function (angular) {
'use strict';
var app = angular.module('app');
app.service('HttpService', ['$http', '$q', "$log", 'PendingRequestsService', function ($http, $q, $log, pendingRequests) {
this.post = function (url, params) {
var deferred = $q.defer();
var xhr = $.ASI.callMethod({
url: url,
data: params,
error: function() {
$log.log("ajax error");
}
});
pendingRequests.add({
url: url,
xhr: xhr,
deferred: deferred
});
xhr.done(function (data, textStatus, jqXhr) {
deferred.resolve(data);
})
.fail(function (jqXhr, textStatus, errorThrown) {
deferred.reject(errorThrown);
}).always(function (dataOrjqXhr, textStatus, jqXhrErrorThrown) {
//Once a request has failed or succeeded, remove it from the pending list
pendingRequests.remove(url);
});
return deferred.promise;
}
}]);
})(window.angular);
Plus tard dans votre service, lorsque vous chargez des données, vous utiliserez HttpService au lieu de $ http:
(function (angular) {
angular.module('app').service('dataService', ["HttpService", function (httpService) {
this.getResources = function (params) {
return httpService.post('/serverMethod', { param: params });
};
}]);
})(window.angular);
Plus tard dans votre code, vous voudriez charger les données:
(function (angular) {
var app = angular.module('app');
app.controller('YourController', ["DataService", "PendingRequestsService", function (httpService, pendingRequestsService) {
dataService
.getResources(params)
.then(function (data) {
// do stuff
});
...
// later that day cancel requests
pendingRequestsService.cancelAll();
}]);
})(window.angular);
L'annulation de requêtes émises avec $http
n'est pas prise en charge par la version actuelle d'AngularJS. Il y a un demande d'extraction ouverte pour ajouter cette fonctionnalité, mais ce PR n'a pas encore été examiné. Il n'est donc pas clair si cela va en faire le noyau d'AngularJS.
Pour une raison quelconque, config.timeout ne fonctionne pas pour moi. J'ai utilisé cette approche:
let cancelRequest = $q.defer();
let cancelPromise = cancelRequest.promise;
let httpPromise = $http.get(...);
$q.race({ cancelPromise, httpPromise })
.then(function (result) {
...
});
Et cancelRequest.resolve () pour annuler. En fait, cela n’annule pas une demande, mais vous n’obtenez pas au moins une réponse inutile.
J'espère que cela t'aides.
Si vous souhaitez annuler les demandes en attente sur stateChangeStart avec ui-router, vous pouvez utiliser quelque chose comme ceci:
// en service
var deferred = $q.defer();
var scope = this;
$http.get(URL, {timeout : deferred.promise, cancel : deferred}).success(function(data){
//do something
deferred.resolve(dataUsage);
}).error(function(){
deferred.reject();
});
return deferred.promise;
// dans la configuration UIrouter
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
//To cancel pending request when change state
angular.forEach($http.pendingRequests, function(request) {
if (request.cancel && request.timeout) {
request.cancel.resolve();
}
});
});
Cela améliore la réponse acceptée en décorant le service $ http avec une méthode d'abandon comme suit ...
'use strict';
angular.module('admin')
.config(["$provide", function ($provide) {
$provide.decorator('$http', ["$delegate", "$q", function ($delegate, $q) {
var getFn = $delegate.get;
var cancelerMap = {};
function getCancelerKey(method, url) {
var formattedMethod = method.toLowerCase();
var formattedUrl = encodeURI(url).toLowerCase().split("?")[0];
return formattedMethod + "~" + formattedUrl;
}
$delegate.get = function () {
var cancelerKey, canceler, method;
var args = [].slice.call(arguments);
var url = args[0];
var config = args[1] || {};
if (config.timeout == null) {
method = "GET";
cancelerKey = getCancelerKey(method, url);
canceler = $q.defer();
cancelerMap[cancelerKey] = canceler;
config.timeout = canceler.promise;
args[1] = config;
}
return getFn.apply(null, args);
};
$delegate.abort = function (request) {
console.log("aborting");
var cancelerKey, canceler;
cancelerKey = getCancelerKey(request.method, request.url);
canceler = cancelerMap[cancelerKey];
if (canceler != null) {
console.log("aborting", cancelerKey);
if (request.timeout != null && typeof request.timeout !== "number") {
canceler.resolve();
delete cancelerMap[cancelerKey];
}
}
};
return $delegate;
}]);
}]);
QUE IS CE CODE FAIT-IL?
Pour annuler une demande, vous devez définir un délai d'attente "promesse". Si aucun délai n'est défini pour la requête HTTP, le code ajoute un délai "promesse". (Si un délai d'attente est déjà défini, rien n'est changé).
Cependant, pour résoudre la promesse, nous avons besoin d’un traitement du "différé". Nous utilisons donc une carte pour pouvoir récupérer le "différé" plus tard. Lorsque nous appelons la méthode d'abandon, le "différé" est extrait de la carte, puis nous appelons la méthode de résolution pour annuler la requête http.
J'espère que ça aide quelqu'un.
LIMITES
Actuellement, cela ne fonctionne que pour $ http.get mais vous pouvez ajouter du code pour $ http.post et ainsi de suite.
COMMENT UTILISER ...
Vous pouvez ensuite l'utiliser, par exemple, lors d'un changement d'état, comme suit ...
rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
angular.forEach($http.pendingRequests, function (request) {
$http.abort(request);
});
});
voici une version qui gère plusieurs demandes, vérifie également le statut annulé dans le rappel pour supprimer les erreurs dans le bloc d’erreurs. (en TypeScript)
niveau du contrôleur:
requests = new Map<string, ng.IDeferred<{}>>();
dans mon http obtenir:
getSomething(): void {
let url = '/api/someaction';
this.cancel(url); // cancel if this url is in progress
var req = this.$q.defer();
this.requests.set(url, req);
let config: ng.IRequestShortcutConfig = {
params: { id: someId}
, timeout: req.promise // <--- promise to trigger cancellation
};
this.$http.post(url, this.getPayload(), config).then(
promiseValue => this.updateEditor(promiseValue.data as IEditor),
reason => {
// if legitimate exception, show error in UI
if (!this.isCancelled(req)) {
this.showError(url, reason)
}
},
).finally(() => { });
}
méthodes d'assistance
cancel(url: string) {
this.requests.forEach((req,key) => {
if (key == url)
req.resolve('cancelled');
});
this.requests.delete(url);
}
isCancelled(req: ng.IDeferred<{}>) {
var p = req.promise as any; // as any because typings are missing $$state
return p.$$state && p.$$state.value == 'cancelled';
}
maintenant, en regardant l'onglet réseau, je vois que cela fonctionne parfaitement. J'ai appelé la méthode 4 fois et seul le dernier est passé.
Vous pouvez ajouter une fonction personnalisée au service $http
à l'aide d'un "décorateur" qui ajouterait la fonction abort()
à vos promesses.
Voici un code de travail:
app.config(function($provide) {
$provide.decorator('$http', function $logDecorator($delegate, $q) {
$delegate.with_abort = function(options) {
let abort_defer = $q.defer();
let new_options = angular.copy(options);
new_options.timeout = abort_defer.promise;
let do_throw_error = false;
let http_promise = $delegate(new_options).then(
response => response,
error => {
if(do_throw_error) return $q.reject(error);
return $q(() => null); // prevent promise chain propagation
});
let real_then = http_promise.then;
let then_function = function () {
return mod_promise(real_then.apply(this, arguments));
};
function mod_promise(promise) {
promise.then = then_function;
promise.abort = (do_throw_error_param = false) => {
do_throw_error = do_throw_error_param;
abort_defer.resolve();
};
return promise;
}
return mod_promise(http_promise);
}
return $delegate;
});
});
Ce code utilise la fonctionnalité de décorateur d'angularjs pour ajouter une fonction with_abort()
au service $http
.
with_abort()
utilise $http
option de délai d'attente qui vous permet d'abandonner une demande http.
La promesse retournée est modifiée pour inclure une fonction abort()
. Il a également un code pour vous assurer que la abort()
fonctionne même si vous chaînez des promesses.
Voici un exemple d'utilisation:
// your original code
$http({ method: 'GET', url: '/names' }).then(names => {
do_something(names));
});
// new code with ability to abort
var promise = $http.with_abort({ method: 'GET', url: '/names' }).then(
function(names) {
do_something(names));
});
promise.abort(); // if you want to abort
Par défaut, lorsque vous appelez abort()
, la demande est annulée et aucun des gestionnaires de promesses ne s'exécute.
Si vous souhaitez que vos gestionnaires d'erreur soient appelés, transmettez true à abort(true)
.
Dans votre gestionnaire d'erreurs, vous pouvez vérifier si "l'erreur" est due à un "abandon" en vérifiant la propriété xhrStatus
. Voici un exemple:
var promise = $http.with_abort({ method: 'GET', url: '/names' }).then(
function(names) {
do_something(names));
},
function(error) {
if (er.xhrStatus === "abort") return;
});