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


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

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

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

8 172

8 ответов:

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

отмена 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);

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

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

почему-то конфигурации.тайм-аут не работает для меня. Я использовал такой подход:

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

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

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

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

надеюсь, что это помогает.

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

ЧТО ЭТОТ КОД ДЕЛАЕТ?

для отмены запроса необходимо установить тайм-аут "promise". Если для HTTP-запроса не установлен тайм-аут, то код добавляет тайм-аут "promise". (Если тайм-аут уже установлен, то ничего не меняется).

однако, чтобы решить обещание, нам нужна ручка на "отложенном". Таким образом, мы используем карту, чтобы мы могли получить "отложено" на потом. Когда мы вызываем метод abort, "отложенный" извлекается из карты, а затем мы вызываем метод resolve для отмены http-запроса.

надеюсь, это кому-то поможет.

ограничения

В настоящее время это работает только для $http.получить, но вы можете добавить код для $http.пост и так далее

КАК ИСПОЛЬЗОВАТЬ ...

затем вы можете использовать его, например, на изменение состояния следующим образом ...

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

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

регулятор уровня:

    requests = new Map<string, ng.IDeferred<{}>>();

в моем 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(() => { });
    }

вспомогательные методы

    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 раза и только последний прошел.

enter image description here

вы можете добавить пользовательскую функцию в $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 для добавления