Как отменить запрос $http в AngularJS?

Учитывая запрос Ajax в AngularJS

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

что является наиболее эффективным способом отменить этот запрос, если запускается другой запрос (тот же бэкэнд, например, разные параметры).

Ответ 1

Эта функция была добавлена ​​в версию 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.

Ответ 2

Отмена запросов, выданных с помощью $http, не поддерживается текущей версией AngularJS. Для добавления этой возможности существует запрос на перенос, но этот PR еще не был рассмотрен, поэтому неясно, собирается ли оно сделать его в ядре AngularJS.

Ответ 3

Отмена Angular $http Ajax с атрибутом timeout не работает в Angular 1.3.15. Для тех, кто не может дождаться, когда это будет исправлено, я делюсь решением jQuery Ajax, заключенным в Angular.

Решение включает в себя две службы:

  • HttpService (оболочка вокруг функции jQuery Ajax);
  • PendingRequestsService (отслеживает ожидающие/открытые запросы Ajax)

Здесь находится служба 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);

Ответ 4

Если вы хотите отменить ожидающие запросы на 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();
          }
       });
    });

Ответ 5

По какой-то причине config.timeout не работает для меня. Я использовал этот подход:

let cancelRequest = $q.defer();
let cancelPromise = cancelRequest.promise;

let httpPromise = $http.get(...);

$q.race({ cancelPromise, httpPromise })
    .then(function (result) {
...
});

Ответ 6

Это улучшает принятый ответ, украсив службу $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);
    });
  });

Ответ 7

вот версия, которая обрабатывает несколько запросов, а также проверяет отмененный статус в обратном вызове для подавления ошибок в блоке ошибок. (в 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 раза, и только последний прошел.

введите описание изображения здесь