Jak anulować żądanie $ http w AngularJS?


190

Biorąc pod uwagę żądanie Ajax w AngularJS

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

jaki jest najskuteczniejszy sposób anulowania tego żądania, jeśli uruchomione zostanie kolejne żądanie (ten sam backend, różne parametry na przykład).


8
Żadna z poniższych odpowiedzi w rzeczywistości nie anuluje samego żądania. Nie ma możliwości anulowania żądania HTTP po opuszczeniu przeglądarki. Wszystkie poniższe odpowiedzi po prostu abonują słuchacza. Żądanie HTTP wciąż trafia na serwer, jest nadal przetwarzane, a serwer nadal wysyła odpowiedź, to tylko przypadek, czy klient nadal lisex za tę odpowiedź, czy nie.
Liam,

Żądanie $ http anuluj przy zmianie trasy freakyjolly.com/how-to-cancel-http-requests-in-angularjs-app
Code Spy


@Liam moje pytanie nie zostało anulowane na serwerze. byłoby to bardzo specyficzne dla technologii / implementacji serwera. martwiłem się porzuceniem oddzwaniania
Sonic Soul,

Odpowiedzi:


326

Ta funkcja została dodana do wersji 1.1.5 za pomocą parametru limitu czasu:

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
co powinienem zrobić, jeśli potrzebuję zarówno limitu czasu, jak i ręcznego anulowania poprzez obietnicę?
Raman Chodźka

15
@ RamanChodźka Możesz zrobić obie z obietnicą; możesz ustawić limit czasu, aby anulować obietnicę po pewnym czasie, za pomocą natywnej setTimeoutfunkcji JavaScript lub $timeoutusługi Angular .
Quinn Strahl,

9
canceler.resolve () anuluje przyszłe żądania. To jest lepsze rozwiązanie: odetocode.com/blogs/scott/archive/2014/04/24/…
Toolkit

7
kolejny dobry przykład bardziej kompletnego rozwiązania od Bena Nadela: bennadel.com/blog/…
Pete

3
Naprawdę nie działa. Czy możesz podać działającą próbkę?
Edward Olamisan

10

Anulowanie Angular $ http Ajax z właściwością timeout nie działa w Angular 1.3.15. Dla tych, którzy nie mogą się doczekać, aby to naprawić, udostępniam rozwiązanie jQuery Ajax opakowane w Angular.

Rozwiązanie obejmuje dwie usługi:

  • HttpService (opakowanie wokół funkcji Ajax jQuery);
  • PendingRequestsService (śledzi oczekujące / otwarte żądania Ajax)

Oto usługa 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);

Usługa 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);

Później w usłudze, gdy ładujesz dane, użyjesz HttpService zamiast $ http:

(function (angular) {

    angular.module('app').service('dataService', ["HttpService", function (httpService) {

        this.getResources = function (params) {

            return httpService.post('/serverMethod', { param: params });

        };
    }]);

})(window.angular);

Później w kodzie chcesz załadować dane:

(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

Anulowanie żądań wydanych za pomocą $httpnie jest obsługiwane w bieżącej wersji AngularJS. Zostało otwarte żądanie ściągnięcia, aby dodać tę możliwość, ale ten PR nie został jeszcze sprawdzony, więc nie jest jasne, czy uda mu się przekształcić w rdzeń AngularJS.


że PR został odrzucony, OP przesłał zaktualizowany tutaj github.com/angular/angular.js/pull/1836
Mark Nadig

I to również zostało zamknięte.
frapontillo

Jego wersja wylądowała w ten sposób . Wciąż próbuję rozgryźć składnię, aby użyć ostatecznej wersji. Chciałbym, żeby PR przyszedł z próbkami użycia! :)
SimplGy,

Strona dokumentacji kątowej docs.quarejs.org/api/ng/service/$http w „Użycie” opisuje ustawienie limitu czasu, a także wspomina, jakie obiekty (obietnica) są akceptowane.
Igor Lino,

6

Jeśli chcesz anulować oczekujące żądania na stateChangeStart za pomocą interfejsu użytkownika, możesz użyć czegoś takiego:

// czynny

                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;

// w konfiguracji 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();
          }
       });
    });

To działało dla mnie - bardzo proste i dodałem kolejne, aby nazwać połączenie, dzięki czemu mogę wybrać połączenie i anulować tylko niektóre połączenia
Simon Dragsbæk

Dlaczego konfiguracja routera interfejsu użytkownika musi wiedzieć, czy request.timeoutjest obecna?
trysis

6

Z jakiegoś powodu config.timeout nie działa dla mnie. Użyłem tego podejścia:

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

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

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

I cancelRequest.resolve (), aby anulować. W rzeczywistości nie anuluje to żądania, ale przynajmniej nie otrzymasz niepotrzebnej odpowiedzi.

Mam nadzieję że to pomoże.


Czy widziałeś błąd SyntaxError { cancelPromise, httpPromise }?
Mephiztopheles,

to jest składnia ES6, możesz wypróbować {c: cancelPromise, h: httpPromise}
Aliaksandr Hmyrak

Rozumiem, obiekt shortinitializer
Mephiztopheles,

3

Zwiększa to akceptowaną odpowiedź, dekorując usługę $ http za pomocą metody przerywania w następujący sposób ...

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

CO DZIAŁA TEN KOD?

Aby anulować żądanie, należy ustawić limit czasu „obietnicy”. Jeśli dla żądania HTTP nie ustawiono limitu czasu, kod dodaje limit czasu „obiecania”. (Jeśli limit czasu jest już ustawiony, nic się nie zmienia).

Aby jednak spełnić obietnicę, potrzebujemy poradzić sobie z „odroczonym”. W ten sposób korzystamy z mapy, aby później odzyskać „odroczone”. Kiedy wywołujemy metodę przerwania, „odroczony” jest pobierany z mapy, a następnie wywołujemy metodę resolve, aby anulować żądanie http.

Mam nadzieję, że to komuś pomoże.

OGRANICZENIA

Obecnie działa to tylko dla $ http.get, ale możesz dodać kod dla $ http.post i tak dalej

JAK UŻYWAĆ ...

Następnie możesz go użyć na przykład przy zmianie stanu, w następujący sposób ...

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

Tworzę aplikację, która uruchamia jednocześnie niektóre żądania HTTP i muszę ręcznie przerwać je wszystkie. Próbowałem twojego kodu, ale przerywa on tylko ostatnie żądanie. Czy zdarzyło Ci się to wcześniej? Każda pomoc będzie mile widziana.
Miguel Trabajo

1
kod tutaj utrzymuje odnośniki do obiektów odroczenia, aby można je było później odzyskać, ponieważ obiekt odroczenia musi wykonać przerwanie. ważną rzeczą przy wyszukiwaniu jest para klucz: wartość. Wartość jest obiektem odroczonym. Klucz jest ciągiem generowanym na podstawie metody żądania / adresu URL. Zgaduję, że przerywasz wiele żądań do tej samej metody / adresu URL. Z tego powodu wszystkie klucze są identyczne i zastępują się na mapie. Musisz zmodyfikować logikę generowania klucza, aby wygenerowana została unikalna, nawet jeśli adres URL / metoda są takie same.
danday74

1
ciąg dalszy z góry ... to nie jest błąd w kodzie, kod obsługuje przerywanie wielu żądań ... ale kod po prostu nigdy nie miał obsługiwać przerywania wielu żądań do tego samego adresu URL przy użyciu tej samej metody http ... ale jeśli poprawisz logikę, powinieneś być w stanie sprawić, by działała dość łatwo.
danday74

1
Dziękuję Ci bardzo! Robiłem wiele próśb na ten sam adres URL, ale z różnymi parametrami, a po tym, jak powiedziałeś o tym, zmieniłem ten wiersz i działało to jak urok!
Miguel Trabajo

1

tutaj jest wersja, która obsługuje wiele żądań, sprawdza również status anulowania w wywołaniu zwrotnym, aby ukryć błędy w bloku błędów. (w maszynopisie)

poziom kontrolera:

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

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

metody pomocnicze

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

teraz patrząc na kartę sieci, widzę, że działa pięknie. zadzwoniłem do tej metody 4 razy i przeszła tylko ostatnia.

wprowadź opis zdjęcia tutaj


req.resolve („anulowane”); nie działa dla mnie, używam wersji 1.7.2. Nawet chcę anulować połączenie, jeśli zostanie ono ponownie wywołane, a pierwsze połączenie będzie nadal w stanie oczekiwania. proszę pomóż. Zawsze chcę podać nowo wywołane dane połączeń, anulując wszystkie oczekujące API tego samego
adresu

1

Możesz dodać niestandardową funkcję do $httpusługi za pomocą „dekoratora”, który dodałby abort()funkcję do twoich obietnic.

Oto działający kod:

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

Ten kod używa funkcji dekoratora angularjs w celu dodania with_abort()funkcji do$http usługi.

with_abort()używa $httpopcji limitu czasu, która pozwala przerwać żądanie HTTP.

Zwrócona obietnica jest modyfikowana w celu włączenia abort()funkcji. Posiada również kod, aby upewnić się, że abort()działa, nawet jeśli łączysz obietnice.

Oto przykład, w jaki sposób chcesz go użyć:

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

Domyślnie po wywołaniu abort()żądanie zostaje anulowane i żaden z programów obsługi obietnic nie jest uruchamiany.

Jeśli chcesz, aby procedury obsługi błędów były nazywane pass true abort(true).

W module obsługi błędów możesz sprawdzić, czy „błąd” był spowodowany „przerwaniem”, sprawdzając xhrStatuswłaściwość. Oto przykład:

var promise = $http.with_abort({ method: 'GET', url: '/names' }).then(
    function(names) {
        do_something(names));
    }, 
    function(error) {
        if (er.xhrStatus === "abort") return;
    });
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.