Wie storniere ich eine $ http-Anfrage in AngularJS?

189

Gegeben eine Ajax-Anfrage in AngularJS

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

Was ist der effektivste Weg, um diese Anforderung abzubrechen, wenn eine andere Anforderung gestartet wird (z. B. dasselbe Backend, unterschiedliche Parameter)?

mpm
quelle
8
Keine der folgenden Antworten storniert die Anfrage selbst. Es gibt keine Möglichkeit, eine HTTP-Anforderung abzubrechen, sobald sie den Browser verlässt. Alle unten stehenden Antworten verlassen den Hörer einfach auf irgendeine Weise. Die HTTP-Anfrage trifft immer noch den Server, wird immer noch verarbeitet und der Server sendet immer noch eine Antwort. Es ist nur ein Fall, ob der Client immer noch nach dieser Antwort sucht oder nicht.
Liam
Code für promise.abort() stackoverflow.com/a/50415480/984780
Luis Perez
@ Liam meine Frage wurde auf dem Server nicht abgebrochen. Das wäre sehr spezifisch für Ihre Servertechnologie / -implementierung. Ich war besorgt, den Rückruf aufzugeben
Sonic Soul

Antworten:

325

Diese Funktion wurde der Version 1.1.5 über einen Timeout-Parameter hinzugefügt :

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
quelle
13
Was soll ich tun, wenn ich sowohl eine Zeitüberschreitung als auch eine manuelle Stornierung per Versprechen benötige?
Raman Chodźka
15
@ RamanChodźka Sie können beides mit einem Versprechen tun; Sie können eine Zeitüberschreitung festlegen, um das Versprechen nach einiger Zeit abzubrechen, entweder mit der nativen JavaScript- setTimeoutFunktion oder mit dem Angular- $timeoutDienst.
Quinn Strahl
9
canceler.resolve () bricht zukünftige Anfragen ab. Dies ist eine bessere Lösung: odetocode.com/blogs/scott/archive/2014/04/24/…
Toolkit
7
Ein weiteres gutes Beispiel für eine vollständigere Lösung von Ben Nadel: bennadel.com/blog/…
Pete
3
Funktioniert nicht wirklich. Könnten Sie eine Arbeitsprobe bereitstellen?
Edward Olamisan
10

Das Abbrechen von Angular $ http Ajax mit der Eigenschaft timeout funktioniert in Angular 1.3.15 nicht. Für diejenigen, die nicht darauf warten können, dass dies behoben wird, teile ich eine in Angular verpackte jQuery Ajax-Lösung.

Die Lösung umfasst zwei Dienste:

  • HttpService (ein Wrapper um die jQuery Ajax-Funktion);
  • PendingRequestsService (verfolgt die ausstehenden / offenen Ajax-Anforderungen)

Hier geht der PendingRequestsService-Dienst:

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

Der HttpService-Dienst:

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

Später in Ihrem Dienst, wenn Sie Daten laden, würden Sie den HttpService anstelle von $ http verwenden:

(function (angular) {

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

        this.getResources = function (params) {

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

        };
    }]);

})(window.angular);

Später in Ihrem Code möchten Sie die Daten laden:

(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);
Edward Olamisan
quelle
9

Das Abbrechen von Anforderungen mit $httpwird mit der aktuellen Version von AngularJS nicht unterstützt. Es wurde eine Pull-Anfrage geöffnet , um diese Funktion hinzuzufügen, aber diese PR wurde noch nicht überprüft, sodass nicht klar ist, ob sie in den AngularJS-Kern gelangen wird.

pkozlowski.opensource
quelle
dass PR abgelehnt wurde, OP eingereicht aktualisiert hier github.com/angular/angular.js/pull/1836
Mark Nadig
Und das war auch geschlossen.
Frapontillo
Eine Version davon landete so . Ich versuche immer noch, die Syntax für die Verwendung der endgültigen Version herauszufinden. Wünschte, die PRs würden mit Verwendungsbeispielen kommen! :)
SimplGy
Die eckige Dokumentationsseite docs.angularjs.org/api/ng/service/$http in der 'Verwendung' beschreibt eine Zeitüberschreitungseinstellung und erwähnt auch, welche Objekte (ein Versprechen) akzeptiert werden.
Igor Lino
6

Wenn Sie ausstehende Anforderungen auf stateChangeStart mit dem UI-Router abbrechen möchten, können Sie Folgendes verwenden:

// im Dienst

                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 der UIrouter-Konfiguration

$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
quelle
Dies funktionierte für mich - sehr einfach und ich fügte einen weiteren hinzu, um den Anruf zu benennen, damit ich den Anruf auswählen und nur einige der Anrufe abbrechen kann
Simon Dragsbæk
Warum muss die UI-Routerkonfiguration wissen, ob sie request.timeoutvorhanden ist?
Tryse
6

Aus irgendeinem Grund funktioniert config.timeout bei mir nicht. Ich habe diesen Ansatz verwendet:

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

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

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

Und cancelRequest.resolve (), um abzubrechen. Eigentlich wird eine Anfrage nicht abgebrochen, aber Sie erhalten zumindest keine unnötige Antwort.

Hoffe das hilft.

Aliaksandr Hmyrak
quelle
Hast du deinen SyntaxError gesehen { cancelPromise, httpPromise }?
Mephiztopheles
Dies ist die ES6-Syntax. Sie können versuchen, {c: cancelPromise, h: httpPromise}
Aliaksandr Hmyrak
Ich sehe, Objekt Shortinitializer
Mephiztopheles
3

Dies verbessert die akzeptierte Antwort, indem der $ http-Dienst wie folgt mit einer Abbruchmethode dekoriert wird ...

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

WAS MACHT DIESER CODE?

Um eine Anfrage abzubrechen, muss ein "Versprechen" -Zeitlimit festgelegt werden. Wenn für die HTTP-Anforderung kein Zeitlimit festgelegt ist, fügt der Code ein "Versprechen" -Zeitlimit hinzu. (Wenn bereits eine Zeitüberschreitung eingestellt ist, wird nichts geändert).

Um das Versprechen zu lösen, brauchen wir jedoch einen Griff auf den "Aufgeschobenen". Wir verwenden daher eine Karte, damit wir die "zurückgestellten" später abrufen können. Wenn wir die Abbruchmethode aufrufen, wird das "Zurückgestellte" aus der Karte abgerufen, und dann rufen wir die Auflösungsmethode auf, um die http-Anforderung abzubrechen.

Hoffe das hilft jemandem.

EINSCHRÄNKUNGEN

Derzeit funktioniert dies nur für $ http.get, Sie können jedoch Code für $ http.post usw. hinzufügen

WIE BENUTZT MAN ...

Sie können es dann beispielsweise bei Statusänderungen wie folgt verwenden ...

rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
  angular.forEach($http.pendingRequests, function (request) {
        $http.abort(request);
    });
  });
danday74
quelle
Ich mache eine App, die einige http-Anfragen gleichzeitig auslöst, und ich muss sie alle manuell abbrechen. Ich habe Ihren Code ausprobiert, aber er bricht nur die letzte Anfrage ab. Ist dir das schon mal passiert? Jede Hilfe wäre dankbar.
Miguel Trabajo
1
Der Code hier enthält eine Suche nach Verweisen auf die verzögerten Objekte, damit diese später abgerufen werden können, da das verzögerte Objekt einen Abbruch durchführen muss. Das Wichtigste bei der Suche ist das Schlüssel-Wert-Paar. Der Wert ist das verzögerte Objekt. Der Schlüssel ist eine Zeichenfolge, die basierend auf der Anforderungsmethode / URL generiert wird. Ich vermute, dass Sie mehrere Anfragen an dieselbe Methode / URL abbrechen. Aus diesem Grund sind alle Schlüssel identisch und überschreiben sich gegenseitig in der Karte. Sie müssen die Logik zur Schlüsselgenerierung so anpassen, dass eine eindeutige generiert wird, auch wenn die URL / Methode identisch ist.
danday74
1
Fortsetzung von oben ... dies ist kein Fehler im Code, der Code behandelt das Abbrechen mehrerer Anforderungen ... aber der Code war einfach nie dazu gedacht, mehrere Anforderungen an dieselbe URL mit derselben http-Methode abzubrechen ... aber Wenn Sie die Logik optimieren, sollten Sie in der Lage sein, sie ziemlich einfach zum Laufen zu bringen.
danday74
1
Vielen Dank! Ich habe mehrere Anfragen an dieselbe URL gestellt, aber mit unterschiedlichen Parametern, und nachdem Sie darüber gesprochen haben, habe ich diese Zeile geändert und es hat wie ein Zauber funktioniert!
Miguel Trabajo
1

Hier ist eine Version, die mehrere Anforderungen verarbeitet und im Rückruf nach dem Status "Abgebrochen" sucht, um Fehler im Fehlerblock zu unterdrücken. (in Typoskript)

Controller-Ebene:

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

in meinem http bekommen:

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

Hilfsmethoden

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

Wenn ich jetzt auf die Registerkarte "Netzwerk" schaue, sehe ich, dass es wunderbar funktioniert. Ich habe die Methode 4 Mal aufgerufen und nur die letzte ist durchgegangen.

Geben Sie hier die Bildbeschreibung ein

Sonic Soul
quelle
req.resolve ('abgebrochen'); funktioniert nicht für mich, ich benutze 1.7.2 Version. Sogar ich möchte einen Anruf abbrechen, wenn er erneut angerufen wird und der erste Anruf noch aussteht. Bitte helfen Sie. Ich möchte immer neu angerufene
Anrufdaten
1

Sie können dem $httpService eine benutzerdefinierte Funktion hinzufügen, indem Sie einen "Dekorator" verwenden, der dieabort() Funktion Ihren Versprechungen .

Hier ist ein Arbeitscode:

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

Dieser Code verwendet die Decorator-Funktionalität von anglejs, um with_abort()dem $httpService eine Funktion hinzuzufügen .

with_abort() Verwendet $http Timeout-Option, mit der Sie eine http-Anforderung abbrechen können.

Das zurückgegebene Versprechen wird geändert, um eine abort()Funktion einzuschließen. Es hat auch Code, um sicherzustellen, dass dieabort() funktioniert, auch wenn Sie Versprechen verketten.

Hier ist ein Beispiel, wie Sie es verwenden würden:

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

Standardmäßig, wenn Sie anrufen abort() die Anforderung abgebrochen und keiner der Versprechen-Handler wird ausgeführt.

Wenn Sie möchten, dass Ihre Fehlerbehandlungsroutinen als pass true to bezeichnet werden abort(true) .

In Ihrem Fehlerbehandler können Sie überprüfen, ob der "Fehler" auf einen "Abbruch" zurückzuführen ist, indem Sie die xhrStatusEigenschaft überprüfen . Hier ist ein Beispiel:

var promise = $http.with_abort({ method: 'GET', url: '/names' }).then(
    function(names) {
        do_something(names));
    }, 
    function(error) {
        if (er.xhrStatus === "abort") return;
    });
Luis Perez
quelle