Come annullare una richiesta $ http in AngularJS?


190

Data una richiesta Ajax in AngularJS

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

qual è il modo più efficace per annullare quella richiesta se viene lanciata un'altra richiesta (stesso back-end, parametri diversi per esempio).


8
Nessuna delle risposte di seguito annulla effettivamente la richiesta stessa. Non è possibile annullare una richiesta HTTP una volta che lascia il browser. Tutte le risposte di seguito semplicemente abbandonano l'ascoltatore in qualche modo. La richiesta HTTP continua a colpire il server, viene comunque elaborata e il server invierà comunque una risposta, è solo un caso in cui il client stia ancora ascoltando la risposta o meno.
Liam,


codice per promise.abort() stackoverflow.com/a/50415480/984780
Luis Perez,

@Liam la mia domanda non si stava annullando sul server. sarebbe molto specifico di quale sia la tecnologia / implementazione del tuo server. ero preoccupato di abbandonare il callback
Sonic Soul il

Risposte:


326

Questa funzione è stata aggiunta alla versione 1.1.5 tramite un parametro di 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
cosa devo fare se ho bisogno di un timeout e di una cancellazione manuale tramite promessa?
Raman Chodźka,

15
@ RamanChodźka Puoi fare entrambe le cose con una promessa; puoi impostare un timeout per annullare la promessa dopo un certo periodo di tempo, con la setTimeoutfunzione nativa di JavaScript o il $timeoutservizio di Angular .
Quinn Strahl,

9
canceler.resolve () annullerà le richieste future. Questa è una soluzione migliore: odetocode.com/blogs/scott/archive/2014/04/24/…
Toolkit

7
un altro buon esempio di una soluzione più completa di Ben Nadel: bennadel.com/blog/…
Pete,

3
Non funziona davvero. Potresti fornire un campione funzionante?
Edward Olamisan,

10

L'annullamento di Angular $ http Ajax con la proprietà timeout non funziona in Angular 1.3.15. Per coloro che non vedono l'ora che venga risolto, condivido una soluzione Ajax jQuery racchiusa in Angular.

La soluzione prevede due servizi:

  • HttpService (un wrapper attorno alla funzione jQuery Ajax);
  • PendingRequestsService (tiene traccia delle richieste Ajax in sospeso / aperte)

Ecco il servizio 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);

Il servizio 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);

Più avanti nel servizio quando si caricano dati, si utilizza HttpService anziché $ http:

(function (angular) {

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

        this.getResources = function (params) {

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

        };
    }]);

})(window.angular);

Più avanti nel tuo codice vorresti caricare i dati:

(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

L'annullamento di richieste emesse con $httpnon è supportato con la versione corrente di AngularJS. È stata aperta una richiesta pull per aggiungere questa funzionalità, ma questa PR non è stata ancora rivista, quindi non è chiaro se diventerà il core di AngularJS.


che PR è stata respinta, OP ha inviato uno aggiornato qui github.com/angular/angular.js/pull/1836
Mark Nadig,

E anche quello è stato chiuso.
frapontillo,

Una versione è arrivata così . Sto ancora cercando di capire la sintassi per usare la versione finale. Vorrei che i PR venissero forniti con campioni di utilizzo! :)
Semplicemente il

La pagina della documentazione angolare docs.angularjs.org/api/ng/service/$http in "Uso" descrive un'impostazione di timeout e menziona anche quali oggetti (una Promessa) sono accettati.
Igor Lino,

6

Se vuoi annullare le richieste in sospeso su stateChangeStart con ui-router, puoi usare qualcosa del genere:

// in servizio

                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;

// in UIrouter config

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

Questo ha funzionato per me - molto semplice e ne ho aggiunto un altro per nominare la chiamata in modo da poter selezionare la chiamata e annullare solo alcune delle chiamate
Simon Dragsbæk

Perché la configurazione del router UI deve sapere se request.timeoutè presente?
trysis,

6

Per qualche motivo config.timeout non funziona per me. Ho usato questo approccio:

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

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

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

E cancelRequest.resolve () per annullare. In realtà non annulla una richiesta ma non si ottiene almeno una risposta non necessaria.

Spero che questo ti aiuti.


Hai visto SyntaxError { cancelPromise, httpPromise }?
Mephiztopheles,

questa è sintassi ES6, puoi provare {c: cancelPromise, h: httpPromise}
Aliaksandr Hmyrak,

Vedo, shortinitializer oggetto
Mephiztopheles,

3

Ciò migliora la risposta accettata decorando il servizio $ http con un metodo di interruzione come segue ...

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

CHE COSA FA QUESTO CODICE?

Per annullare una richiesta è necessario impostare un timeout "promessa". Se sulla richiesta HTTP non è impostato alcun timeout, il codice aggiunge un timeout "promettente". (Se è già stato impostato un timeout, non viene modificato nulla).

Tuttavia, per risolvere la promessa abbiamo bisogno di una maniglia sul "differito". Usiamo quindi una mappa in modo da poter recuperare il "differito" in seguito. Quando chiamiamo il metodo di interruzione, il "differito" viene recuperato dalla mappa e quindi chiamiamo il metodo di risoluzione per annullare la richiesta http.

Spero che questo aiuti qualcuno.

LIMITAZIONI

Attualmente funziona solo per $ http.get ma è possibile aggiungere codice per $ http.post e così via

COME USARE ...

È quindi possibile utilizzarlo, ad esempio, al cambio di stato, come segue ...

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

Sto realizzando un'app che genera contemporaneamente alcune richieste HTTP e devo interromperle manualmente. Ho provato il tuo codice ma interrompe solo l'ultima richiesta. Ti è successo prima? Qualsiasi aiuto sarebbe apprezzato.
Miguel Trabajo,

1
il codice qui mantiene una ricerca di riferimenti agli oggetti differiti in modo che possano essere recuperati in un secondo momento poiché è necessario che l'oggetto differito esegua un annullamento. l'importante con la ricerca è la chiave: coppia di valori. Il valore è l'oggetto differito. La chiave è una stringa generata in base al metodo / url di richiesta. Immagino che stai interrompendo più richieste con lo stesso metodo / URL. Per questo motivo tutti i tasti sono identici e si sovrascrivono l'un l'altro nella mappa. È necessario modificare la logica di generazione delle chiavi in ​​modo che ne venga generata una unica anche se url / metodo sono gli stessi.
danday74

1
continua dall'alto ... questo non è un bug nel codice, il codice gestisce l'interruzione di più richieste ... ma il codice non è stato semplicemente concepito per gestire l'interruzione di più richieste nello stesso URL utilizzando lo stesso metodo http ... ma se modifichi la logica dovresti riuscire a farlo funzionare abbastanza facilmente.
danday74

1
Grazie mille! Stavo facendo più richieste allo stesso url ma con parametri diversi, e dopo aver detto che ho cambiato quella linea e ha funzionato come un fascino!
Miguel Trabajo,

1

ecco una versione che gestisce più richieste, controlla anche lo stato annullato nel callback per sopprimere gli errori nel blocco degli errori. (in dattiloscritto)

livello del controller:

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

nel mio http ottenere:

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

metodi di supporto

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

ora guardando la scheda di rete, vedo che funziona beatuifully. ho chiamato il metodo 4 volte e solo l'ultimo è passato.

inserisci qui la descrizione dell'immagine


req.resolve ( 'cancellata'); non funziona per me, sto usando la versione 1.7.2. Anche io voglio annullare una chiamata se viene chiamata di nuovo e la prima chiamata è ancora in stato di attesa. per favore aiuto. voglio sempre fornire i dati delle chiamate appena chiamate cancellando tutte le API in attesa dello stesso URL
Sudarshan Kalebere,

1

È possibile aggiungere una funzione personalizzata al $httpservizio utilizzando un "decoratore" che aggiungerebbeabort() funzione alle tue promesse.

Ecco un po 'di codice funzionante:

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

Questo codice utilizza la funzionalità di decorazione di angularjs per aggiungere una with_abort()funzione al $httpservizio.

with_abort()utilizza l' $httpopzione di timeout che consente di interrompere una richiesta http.

La promessa restituita viene modificata per includere una abort()funzione. Ha anche un codice per assicurarsi che abort()funzioni anche se la catena promette.

Ecco un esempio di come lo useresti:

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

Per impostazione predefinita, quando si chiama abort()la richiesta viene annullata e nessuno dei gestori delle promesse viene eseguito.

Se si desidera che i gestori degli errori vengano chiamati pass true abort(true).

Nel gestore degli errori è possibile verificare se "errore" era dovuto a un "interruzione" controllando la xhrStatusproprietà. Ecco un esempio:

var promise = $http.with_abort({ method: 'GET', url: '/names' }).then(
    function(names) {
        do_something(names));
    }, 
    function(error) {
        if (er.xhrStatus === "abort") return;
    });
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.