Учитывая запрос Ajax в AngularJS
$http.get("/backend/").success(callback);
что является наиболее эффективным способом отменить этот запрос, если запускается другой запрос (тот же бэкэнд, например, разные параметры).
Учитывая запрос Ajax в AngularJS
$http.get("/backend/").success(callback);
что является наиболее эффективным способом отменить этот запрос, если запускается другой запрос (тот же бэкэнд, например, разные параметры).
Эта функция была добавлена в версию 1.1.5 через параметр таймаута:
var canceler = $q.defer();
$http.get('/someUrl', {timeout: canceler.promise}).success(successCallback);
// later...
canceler.resolve(); // Aborts the $http request if it isn't finished.
Отмена запросов, выданных с помощью $http
, не поддерживается текущей версией AngularJS. Для добавления этой возможности существует запрос на перенос, но этот PR еще не был рассмотрен, поэтому неясно, собирается ли оно сделать его в ядре AngularJS.
Отмена Angular $http Ajax с атрибутом timeout не работает в Angular 1.3.15. Для тех, кто не может дождаться, когда это будет исправлено, я делюсь решением jQuery Ajax, заключенным в Angular.
Решение включает в себя две службы:
Здесь находится служба 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);
Служба 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);
Позже, когда вы загружаете данные, вы должны использовать HttpService вместо $http:
(function (angular) {
angular.module('app').service('dataService', ["HttpService", function (httpService) {
this.getResources = function (params) {
return httpService.post('/serverMethod', { param: params });
};
}]);
})(window.angular);
Позже в вашем коде вы хотели бы загрузить данные:
(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);
Если вы хотите отменить ожидающие запросы на stateChangeStart с помощью ui-router, вы можете использовать что-то вроде этого:
//в сервисе
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;
//в конфигурации 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();
}
});
});
По какой-то причине config.timeout не работает для меня. Я использовал этот подход:
let cancelRequest = $q.defer();
let cancelPromise = cancelRequest.promise;
let httpPromise = $http.get(...);
$q.race({ cancelPromise, httpPromise })
.then(function (result) {
...
});
Это улучшает принятый ответ, украсив службу $http методом прерывания следующим образом:
'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;
}]);
}]);
ЧТО ТАКОЕ ЭТОТ КОД?
Чтобы отменить запрос, должен быть установлен тайм-аут "обещание". Если в HTTP-запросе не установлен тайм-аут, тогда код добавляет тайм-аут "обещания". (Если тайм-аут уже установлен, ничего не изменяется).
Однако для решения обещания нам нужен дескриптор "отложен". Таким образом, мы используем карту, поэтому мы можем позже получить "отложенную". Когда мы вызываем метод прерывания, "отложенный" извлекается с карты, а затем мы вызываем метод разрешения для отмены HTTP-запроса.
Надеюсь, это поможет кому-то.
ОГРАНИЧЕНИЯ
В настоящее время это работает только для $http.get, но вы можете добавить код для $http.post и т.д.
КАК ИСПОЛЬЗОВАТЬ...
Затем вы можете использовать его, например, при изменении состояния, следующим образом...
rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
angular.forEach($http.pendingRequests, function (request) {
$http.abort(request);
});
});
вот версия, которая обрабатывает несколько запросов, а также проверяет отмененный статус в обратном вызове для подавления ошибок в блоке ошибок. (в Typescript)
Уровень контроллера:
requests = new Map<string, ng.IDeferred<{}>>();
в моем http:
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(() => { });
}
вспомогательные методы
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';
}
теперь смотря на вкладку сети, я вижу, что она работает блательно. я назвал метод 4 раза, и только последний прошел.