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

190

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

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

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

MPM
источник
8
Ни один из ответов ниже фактически не отменяет сам запрос. Невозможно отменить HTTP-запрос, как только он покидает браузер. Все приведенные ниже ответы просто так или иначе слушают слушателя. HTTP-запрос все еще попадает на сервер, все еще обрабатывается, и сервер все равно будет отправлять ответ, это всего лишь случай, если клиент все еще хочет получить ответ или нет.
Лиам
$ http запрос на отмену изменения маршрута freakyjolly.com/how-to-cancel-http-requests-in-angularjs-app
Code Spy
код для promise.abort() stackoverflow.com/a/50415480/984780
Луис Перес
@ Лиам, мой вопрос не отменялся на сервере. это будет очень специфично для вашей серверной технологии / реализации. я был обеспокоен отказом от обратного вызова
Sonic Soul

Ответы:

326

Эта функция была добавлена ​​в выпуск 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.
lambinator
источник
14
Что мне делать, если мне нужно как тайм-аут, так и ручная отмена через обещание?
Раман Чодёка
15
@ RamanChodźka Вы можете сделать оба с обещанием; Вы можете установить тайм-аут, чтобы отменить обещание через некоторое время, либо с помощью встроенной setTimeoutфункции JavaScript, либо с помощью $timeoutслужбы Angular .
Куинн Страл
9
canceller.resolve () отменит будущие запросы. Это лучшее решение: odetocode.com/blogs/scott/archive/2014/04/24/…
Инструментарий
7
Еще один хороший пример более полного решения от Бена Наделя
Пит
3
На самом деле не работает. Не могли бы вы предоставить рабочий образец?
Эдвард Оламисан
10

Отмена 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);
Эдвард Оламисан
источник
9

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

pkozlowski.opensource
источник
что PR был отклонен, OP представил обновленный здесь github.com/angular/angular.js/pull/1836
Марк Надиг
И это было закрыто.
Фрапонтильо
Версия этого приземлилась как это . Все еще пытаюсь выяснить синтаксис, чтобы использовать окончательную версию. Жаль, что PR пришли с примерами использования! :)
SimplGy
Страница угловой документации docs.angularjs.org/api/ng/service/$http в разделе «Использование» описывает настройку тайм-аута, а также упоминает, какие объекты (обещания) принимаются.
Игорь Лино
6

Если вы хотите отменить отложенные запросы на 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();
          }
       });
    });
SonTL
источник
Это сработало для меня - очень просто, и я добавил еще один, чтобы назвать звонок, чтобы я мог выбрать звонок и отменить только некоторые звонки
Simon Dragsbæk
Почему конфигурация UI Router должна знать, request.timeoutприсутствует ли?
trysis
6

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

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

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

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

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

Надеюсь это поможет.

Александр хмырак
источник
Вы видели вашу ошибку SyntaxError { cancelPromise, httpPromise }?
Мефизофель
это синтаксис ES6, вы можете попробовать {c: cancelPromise, h: httpPromise}
Александр Хмырак
Я вижу, объект shortinitializer
Мефизофель
3

Это расширяет принятый ответ, украшая службу $ 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-запроса тайм-аут не установлен, то код добавляет тайм-аут «обещания». (Если тайм-аут уже установлен, то ничего не меняется).

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

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

ОГРАНИЧЕНИЯ

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

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

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

rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
  angular.forEach($http.pendingRequests, function (request) {
        $http.abort(request);
    });
  });
danday74
источник
Я делаю приложение, которое запускает несколько http-запросов одновременно, и мне нужно вручную прервать их все. Я пробовал ваш код, но он отменяет только последний запрос. Это случилось с тобой раньше? Любая помощь будет оценена.
Мигель Трабахо
1
здесь код поддерживает поиск ссылок на объекты отсрочки, чтобы их можно было извлечь позже, поскольку объект отсрочки должен сделать прерывание. при поиске важна пара ключ: значение. Значением является объект отсрочки. Ключ представляет собой строку, сгенерированную на основе метода запроса / URL-адреса. Я предполагаю, что вы прерываете несколько запросов к одному и тому же методу / URL. Из-за этого все ключи идентичны, и они перезаписывают друг друга на карте. Вам необходимо настроить логику генерации ключа, чтобы генерировалась уникальная логика, даже если URL / метод совпадают.
danday74
1
продолжение сверху ... это не ошибка в коде, код обрабатывает прерывание нескольких запросов ... но код просто никогда не предназначался для обработки нескольких запросов на один и тот же URL-адрес с использованием одного и того же метода http ... но если вы настроите логику, вы сможете довольно легко заставить ее работать.
danday74
1
Большое спасибо! Я делал несколько запросов на один и тот же URL-адрес, но с разными параметрами, и после того, как вы сказали об этом, я изменил эту строку, и это сработало как шарм!
Мигель Трабахо,
1

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

Уровень контроллера:

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

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

Sonic Soul
источник
req.resolve ( 'отменен'); у меня не работает, я использую версию 1.7.2. Даже я хочу отменить звонок, если он снова вызывается и первый звонок все еще находится в состоянии ожидания. пожалуйста помоги. я всегда хочу предоставить недавно вызванные данные о вызовах, отменив все ожидающие API одного и того же URL-
адреса
1

Вы можете добавить пользовательскую функцию к $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()запрос отменяется, и ни один из обработчиков обещаний не запускается.

Если вы хотите, чтобы ваши обработчики ошибок вызывались, передайте true 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;
    });
Луис Перес
источник