Bei einer Ajax-Anfrage in AngularJS
$http.get("/backend/").success(callback);
was ist der effektivste Weg, um diese Anfrage zu stornieren, wenn eine andere Anfrage gestartet wird (dasselbe Backend, zum Beispiel andere Parameter).
Diese Funktion wurde zur Version 1.1.5 hinzugefügt über einen Timeout-Parameter:
var canceler = $q.defer();
$http.get('/someUrl', {timeout: canceler.promise}).success(successCallback);
// later...
canceler.resolve(); // Aborts the $http request if it isn't finished.
Abbrechen Angular $ http Ajax mit der Timeout-Eigenschaft funktioniert nicht in Angular 1.3.15. Für diejenigen, die nicht darauf warten können, dass dies behoben wird, bin ich Freigabe einer in Angular verpackten jQuery Ajax-Lösung.
Die Lösung umfasst zwei Dienste:
Hier geht der PendingRequestsService-Dienst:
(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);
Der HttpService-Dienst:
(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);
Später in Ihrem Dienst, wenn Sie Daten laden, würden Sie den HttpService anstelle von $ http verwenden:
(function (angular) {
angular.module('app').service('dataService', ["HttpService", function (httpService) {
this.getResources = function (params) {
return httpService.post('/serverMethod', { param: params });
};
}]);
})(window.angular);
Später in Ihrem Code möchten Sie die Daten laden:
(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);
Stornierung von Anfragen mit $http
wird mit der aktuellen Version von AngularJS nicht unterstützt. Es gibt eine Pull-Anfrage geöffnet , um diese Funktion hinzuzufügen, aber diese PR wurde noch nicht überprüft, sodass nicht klar ist, ob sie in den AngularJS-Kern gelangen wird.
Aus irgendeinem Grund funktioniert config.timeout bei mir nicht. Ich habe diesen Ansatz verwendet:
let cancelRequest = $q.defer();
let cancelPromise = cancelRequest.promise;
let httpPromise = $http.get(...);
$q.race({ cancelPromise, httpPromise })
.then(function (result) {
...
});
Und cancelRequest.resolve () zum Abbrechen. Tatsächlich wird eine Anfrage nicht storniert, aber Sie erhalten zumindest keine unnötige Antwort.
Hoffe das hilft.
Wenn Sie ausstehende Anforderungen auf stateChangeStart mit ui-router stornieren möchten, können Sie Folgendes verwenden:
// im Dienst
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;
// in der UIrouter Konfiguration
$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();
}
});
});
Dies verbessert die akzeptierte Antwort, indem der $ http-Dienst wie folgt mit einer Abbruchmethode dekoriert wird ...
'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;
}]);
}]);
WAS IS DIESER CODE?
Um eine Anfrage zu stornieren, muss ein "Versprechen" -Zeitlimit festgelegt werden. Wenn in der HTTP-Anforderung kein Zeitlimit festgelegt ist, fügt der Code ein Zeitlimit für "Versprechen" hinzu. (Wenn bereits eine Zeitüberschreitung eingestellt ist, wird nichts geändert).
Um das Versprechen zu lösen, brauchen wir jedoch einen Griff auf das "Aufgeschobene". Wir verwenden daher eine Karte, damit wir die "zurückgestellte" später abrufen können. Wenn wir die Abbruchmethode aufrufen, wird die "verzögerte" Methode von der Karte abgerufen und dann die Auflösungsmethode aufgerufen, um die http-Anforderung abzubrechen.
Hoffe das hilft jemandem.
EINSCHRÄNKUNGEN
Derzeit funktioniert dies nur für $ http.get, aber Sie können Code für $ http.post usw. hinzufügen
WIE BENUTZT MAN ...
Sie können es dann beispielsweise bei einer Statusänderung wie folgt verwenden ...
rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
angular.forEach($http.pendingRequests, function (request) {
$http.abort(request);
});
});
in dieser Version werden mehrere Anforderungen verarbeitet. Außerdem wird im Rückruf nach dem Status "Abgebrochen" gesucht, um Fehler im Fehlerblock zu unterdrücken. (in TypeScript)
controller-Ebene:
requests = new Map<string, ng.IDeferred<{}>>();
in meinem http bekommen:
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(() => { });
}
hilfsmethoden
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';
}
wenn ich jetzt auf die Registerkarte "Netzwerk" schaue, sehe ich, dass es wunderbar funktioniert. ich habe die methode 4 mal aufgerufen und nur die letzte durchlaufen.
Sie können dem Dienst $http
Eine benutzerdefinierte Funktion hinzufügen, indem Sie einen "Dekorator" verwenden, der Ihre Versprechen um die Funktion abort()
erweitert.
Hier ist ein funktionierender Code:
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;
});
});
In diesem Code wird die Dekorationsfunktion von anglejs verwendet, um eine with_abort()
-Funktion zum Dienst $http
Hinzuzufügen.
with_abort()
verwendet die Timeout-Option $http
, mit der Sie eine http-Anfrage abbrechen können.
Das zurückgegebene Versprechen wurde dahingehend geändert, dass es eine abort()
-Funktion enthält. Es enthält auch Code, der sicherstellt, dass abort()
funktioniert, auch wenn Sie Versprechen verketten.
Hier ist ein Beispiel, wie Sie es verwenden würden:
// 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
Wenn Sie abort()
aufrufen, wird die Anforderung standardmäßig abgebrochen und keiner der Promise-Handler ausgeführt.
Wenn Sie möchten, dass Ihre Fehlerbehandlungsroutinen als "wahr" bezeichnet werden, übergeben Sie "wahr" an abort(true)
.
In Ihrer Fehlerbehandlungsroutine können Sie überprüfen, ob der "Fehler" auf einen "Abbruch" zurückzuführen ist, indem Sie die Eigenschaft xhrStatus
überprüfen. Hier ist ein Beispiel:
var promise = $http.with_abort({ method: 'GET', url: '/names' }).then(
function(names) {
do_something(names));
},
function(error) {
if (er.xhrStatus === "abort") return;
});