Як скасувати запит $ http у AngularJS?


190

Дано запит Ajax в AngularJS

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

який найефективніший спосіб скасувати цей запит, якщо запускається інший запит (той же бекенд, наприклад, різні параметри).


8
Жодна з наведених нижче відповідей насправді не скасовує сам запит. Неможливо скасувати запит HTTP, як тільки він вийде з браузера. Усі наведені нижче відповіді просто відмовляються від слухача певним чином. HTTP-запит все ще потрапляє на сервер, він все ще обробляється, і сервер все одно буде надсилати відповідь, це лише випадок, коли клієнт все ще лежить на цю відповідь чи ні.
Ліам

$ http скасувати запит на зміну маршруту freakyjolly.com/how-to-cancel-http-requests-in-angularjs-app
Код шпигуна


@Liam моє запитання не скасовувалося на сервері. це було б дуже специфічно для вашої технології / реалізації сервера. я був стурбований
Sonic Soul

Відповіді:


326

Ця функція була додана до випуску 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.

14
що робити у випадку, якщо мені знадобиться як тайм-аут, так і скасування вручну через обіцянку?
Raman Chodźka

15
@ RamanChodźka Ви можете обійтися як з обіцянками; ви можете встановити тайм-аут, щоб скасувати обіцянку через деякий час, або за допомогою власної setTimeoutфункції JavaScript або служби Angular $timeout.
Квінн Шрель

9
Canceler.resolve () скасує майбутні запити. Це краще рішення: odetocode.com/blogs/scott/archive/2014/04/24/…
Інструментарій

7
ще один хороший приклад більш повного рішення від Ben Nadel
Піт

3
Насправді не працює. Чи можете ви надати робочу вибірку?
Едвард Оламісан

10

Скасування Angular $ http Ajax з властивістю таймауту не працює в 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.


що PR було відхилено, ОП подало оновлене тут github.com/angular/angular.js/pull/1836
Марк

І це теж було закрито.
frapontillo

Версія його приземлилася так . Ще намагаюся з'ясувати синтаксис, щоб використовувати остаточну версію. Побажайте, що ПР прийшли із зразками використання! :)
SimplGy

Сторінка кутової документації docs.angularjs.org/api/ng/service/$http у розділі "Використання" описує налаштування тайм-ауту, а також згадує, які об'єкти (Обіцянка) прийняті.
Ігор Ліно

6

Якщо ви хочете скасувати очікувані запити на 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();
          }
       });
    });

Це працювало для мене - дуже просто, і я додав ще один, щоб назвати виклик, щоб я міг вибрати дзвінок і скасувати лише деякі дзвінки
Simon Dragsbæk

Чому конфігуратор маршрутизатора інтерфейсу повинен знати, чи 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) {
...
});

І cancelRequest.resolve () скасувати. Насправді він не скасовує запит, але ви принаймні не отримуєте зайвої відповіді.

Сподіваюся, це допомагає.


Ви бачили ваш SyntaxError { cancelPromise, httpPromise }?
Мефістофель

це синтаксис ES6, ви можете спробувати {c: cancelPromise, h: httpPromise}
Олександр Гмирак

Я бачу, об'єкт короткого ініціалізатора
Мефістофель

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 не встановлено жодного тайм-ауту, код додає "очікуваний" тайм-аут. (Якщо час встановлено, тоді нічого не змінюється).

Однак для вирішення обіцянки нам потрібна ручка на "відкладеному". Таким чином, ми використовуємо карту, щоб потім отримати "відкладені". Коли ми викликаємо метод переривання, "відкладений" отримується з карти, а потім ми викликаємо метод вирішення, щоб скасувати http-запит.

Сподіваюся, що це комусь допоможе.

ОБМЕЖЕННЯ

В даний час це працює лише для $ http.get, але ви можете додати код для $ http.post тощо

ЯК ВИКОРИСТОВУВАТИ ...

Потім ви можете використовувати його, наприклад, для зміни стану, наступним чином ...

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

Я роблю додаток, яке одночасно запускає кілька запитів http, і мені потрібно вручну перервати їх усіх. Я спробував ваш код, але він скасовує лише останній запит. Це сталося з вами раніше? Будь-яка допомога буде вдячна.
Мігель Трабаджо

1
код тут підтримує пошук посилань на відкладені об'єкти, щоб їх можна було отримати пізніше, оскільки відкладений об'єкт необхідний для відміни. важливою справою при пошуку є ключ: пара значень. Значення - об’єкт відкладу. Ключ - це рядок, згенерований на основі методу запиту / URL-адреси. Я здогадуюсь, що ви перериваєте кілька запитів на один і той же метод / URL. Через це всі ключі однакові, і вони переписують один одного на карті. Вам потрібно налаштувати логіку генерації ключів, щоб створювалася унікальна, навіть якщо URL / метод однакові.
понеділок74

1
продовжувалося зверху ... це не помилка в коді, код обробляє переривання декількох запитів ... але код просто ніколи не мав на увазі вирішувати питання про переривання декількох запитів на один і той же URL-адрес, використовуючи той же метод http ... якщо ви налаштуєте логіку, ви повинні мати можливість досить легко працювати.
понеділок74

1
Велике спасибі! Я робив кілька запитів до однієї URL-адреси, але з різними параметрами, і після того, як ви сказали про це, я змінив цей рядок, і це спрацювало як шарм!
Мігель Трабаджо

1

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

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


req.resolve ('скасовано'); не працює для мене, я використовую версію 1.7.2. Навіть я хочу скасувати дзвінок, якщо він дзвонить ще раз, а перший дзвінок все ще знаходиться в стані очікування. будь ласка, допоможіть. я завжди хочу надати дані про дзвінки, щойно передзвонюються, скасовуючи всі очікувані api однієї URL-адреси
Sudarshan Kalebere,

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()запит скасовується, і жоден з обробників обіцянок не працює.

Якщо ви хочете, щоб ваші оброблячі помилок викликали, передайте істинну адресу 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;
    });
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.