Como cancelar uma solicitação $ http no AngularJS?

Dado um pedido de Ajax no AngularJS

$http.get("/backend/").success(callback); 

qual é a maneira mais eficiente de cancelar essa solicitação se outra solicitação for iniciada (mesmo backend, parâmetros diferentes, por exemplo).

Esse recurso foi adicionado à versão 1.1.5 por meio de um parâmetro de tempo limite:

 var canceler = $q.defer(); $http.get('/someUrl', {timeout: canceler.promise}).success(successCallback); // later... canceler.resolve(); // Aborts the $http request if it isn't finished. 

Cancelar Angular $ http Ajax com a propriedade timeout não funciona no Angular 1.3.15. Para aqueles que não podem esperar que isso seja corrigido, estou compartilhando uma solução jQuery Ajax em Angular.

A solução envolve dois serviços:

  • HttpService (um wrapper em torno da function jQuery Ajax);
  • PendingRequestsService (rastreia as solicitações Ajax pendentes / abertas)

Aqui vai o serviço 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); 

O serviço 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); 

Mais tarde, quando você estiver carregando dados, você usaria o HttpService em vez de $ http:

 (function (angular) { angular.module('app').service('dataService', ["HttpService", function (httpService) { this.getResources = function (params) { return httpService.post('/serverMethod', { param: params }); }; }]); })(window.angular); 

Posteriormente no seu código você gostaria de carregar os dados:

 (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); 

O cancelamento de solicitações emitidas com $http não é suportado com a versão atual do AngularJS. Há uma solicitação de pull aberta para adicionar esse recurso, mas esse PR ainda não foi revisado, portanto, não está claro se ele será incluído no núcleo do AngularJS.

Se você quiser cancelar solicitações pendentes no stateChangeStart com o ui-roteador, você pode usar algo assim:

// em serviço

  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; 

// na configuração do 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(); } }); }); 

Por alguma razão, config.timeout não funciona para mim. Eu usei essa abordagem:

 let cancelRequest = $q.defer(); let cancelPromise = cancelRequest.promise; let httpPromise = $http.get(...); $q.race({ cancelPromise, httpPromise }) .then(function (result) { ... }); 

Isso aprimora a resposta aceita ao decorar o serviço $ http com um método de interrupção da seguinte forma …

 '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; }]); }]); 

O QUE É ESTE CÓDIGO FAZER?

Para cancelar uma solicitação, um tempo limite de “promise” deve ser definido. Se nenhum tempo limite for definido na solicitação HTTP, o código adicionará um tempo limite “prometido”. (Se um tempo limite já estiver definido, nada será alterado).

No entanto, para resolver a promise, precisamos lidar com o “diferido”. Assim, usamos um mapa para recuperar o “adiado” mais tarde. Quando chamamos o método abort, o “adiado” é recuperado do mapa e, em seguida, chamamos o método resolve para cancelar a solicitação http.

Espero que isso ajude alguém.

LIMITAÇÕES

Atualmente isso só funciona para $ http.get, mas você pode adicionar código para $ http.post e assim por diante

COMO USAR …

Você pode então usá-lo, por exemplo, na mudança de estado, como segue …

 rootScope.$on('$stateChangeStart', function (event, toState, toParams) { angular.forEach($http.pendingRequests, function (request) { $http.abort(request); }); }); 

Aqui está uma versão que lida com várias solicitações, também verifica o status cancelado no retorno de chamada para suprimir erros no bloco de erros. (em Typescript)

nível do controlador:

  requests = new Map>(); 

no meu http get:

  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(() => { }); } 

methods auxiliares

  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'; } 

Agora, olhando para a guia de rede, vejo que funciona beatuifully. Eu chamei o método 4 vezes e somente o último passou.

insira a descrição da imagem aqui

Você pode adicionar uma function personalizada ao serviço $http usando um “decorador” que adicionaria a function abort() às suas promises.

Aqui está algum código de trabalho:

 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; }); }); 

Esse código usa a funcionalidade de decorador do angularjs para adicionar uma function with_abort() ao serviço $http .

with_abort() usa a opção $http timeout que permite que você aborte uma solicitação http.

A promise retornada é modificada para include uma function abort() . Ele também tem código para se certificar de que o abort() funciona mesmo se você prometer cadeia.

Aqui está um exemplo de como você o usaria:

 // 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 

Por padrão, quando você chama abort() a solicitação é cancelada e nenhum dos manipuladores de promise é executado.

Se você quiser que seus manipuladores de erros sejam chamados true true para abort(true) .

Em seu manipulador de erros, você pode verificar se o “erro” foi devido a uma “anulação” verificando a propriedade xhrStatus . Aqui está um exemplo:

 var promise = $http.with_abort({ method: 'GET', url: '/names' }).then( function(names) { do_something(names)); }, function(error) { if (er.xhrStatus === "abort") return; }); 
Intereting Posts