Дано запит Ajax в AngularJS
$http.get("/backend/").success(callback);
який найефективніший спосіб скасувати цей запит, якщо запускається інший запит (той же бекенд, наприклад, різні параметри).
promise.abort() stackoverflow.com/a/50415480/984780
Дано запит Ajax в AngularJS
$http.get("/backend/").success(callback);
який найефективніший спосіб скасувати цей запит, якщо запускається інший запит (той же бекенд, наприклад, різні параметри).
promise.abort() stackoverflow.com/a/50415480/984780
Відповіді:
Ця функція була додана до випуску 1.1.5 за допомогою параметра timeout:
var canceler = $q.defer();
$http.get('/someUrl', {timeout: canceler.promise}).success(successCallback);
// later...
canceler.resolve(); // Aborts the $http request if it isn't finished.
setTimeoutфункції JavaScript або служби Angular $timeout.
Скасування Angular $ http Ajax з властивістю таймауту не працює в 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);
Скасування запитів, виданих з $http, не підтримується в поточній версії AngularJS. Для додавання цієї можливості відкрито запит на витяг, але цей PR ще не був розглянений, тому не ясно, чи буде він перетворюватися на ядро AngularJS.
Якщо ви хочете скасувати очікувані запити на stateChangeStart за допомогою ui-роутера, ви можете використовувати щось подібне:
// на службі
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();
}
});
});
request.timeoutвін присутній?
Чомусь config.timeout не працює для мене. Я використовував такий підхід:
let cancelRequest = $q.defer();
let cancelPromise = cancelRequest.promise;
let httpPromise = $http.get(...);
$q.race({ cancelPromise, httpPromise })
.then(function (result) {
...
});
І cancelRequest.resolve () скасувати. Насправді він не скасовує запит, але ви принаймні не отримуєте зайвої відповіді.
Сподіваюся, це допомагає.
{ cancelPromise, httpPromise }?
Це посилює прийняту відповідь, прикрасивши послугу $ 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 рази і пройшов лише останній.
Ви можете додати в $httpслужбу користувацьку функцію за допомогою "декоратора", який додав би abort()функцію вашим обіцянкам.
Ось якийсь робочий код:
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;
});
});
Цей код використовує функцію декоратора angularjs, щоб додати with_abort()функцію до $httpсервісу.
with_abort()використовує $httpпараметр тайм-ауту, який дозволяє перервати запит http.
Повернута обіцянка модифікується, щоб включати abort()функцію. Він також має код, щоб переконатися, що він abort()працює, навіть якщо ви обіцяєте обіцянки.
Ось приклад того, як ви його використали:
// 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
За замовчуванням, коли ви телефонуєте, abort()запит скасовується, і жоден з обробників обіцянок не працює.
Якщо ви хочете, щоб ваші оброблячі помилок викликали, передайте істинну адресу abort(true).
У вашому оброблювачі помилок ви можете перевірити, чи "помилка" сталася через "перервати", перевіривши xhrStatusвластивість. Ось приклад:
var promise = $http.with_abort({ method: 'GET', url: '/names' }).then(
function(names) {
do_something(names));
},
function(error) {
if (er.xhrStatus === "abort") return;
});