web-dev-qa-db-de.com

Wie storniere ich eine $ http-Anfrage in AngularJS?

Bei einer Ajax-Anfrage in AngularJS

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

was ist der effektivste Weg, um diese Anfrage zu stornieren, wenn eine andere Anfrage gestartet wird (dasselbe Backend, zum Beispiel andere Parameter).

183
mpm

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

var canceler = $q.defer();
$http.get('/someUrl', {timeout: canceler.promise}).success(successCallback);
// later...
canceler.resolve();  // Aborts the $http request if it isn't finished.
313
lambinator

Abbrechen Angular $ http Ajax mit der Timeout-Eigenschaft funktioniert nicht in Angular 1.3.15. Für diejenigen, die nicht darauf warten können, dass dies behoben wird, bin ich Freigabe einer in Angular verpackten 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);
10
Edward Olamisan

Stornierung von Anfragen mit $http wird mit der aktuellen Version von AngularJS nicht unterstützt. Es gibt 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.

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 () zum Abbrechen. Tatsächlich wird eine Anfrage nicht storniert, aber Sie erhalten zumindest keine unnötige Antwort.

Hoffe das hilft.

6

Wenn Sie ausstehende Anforderungen auf stateChangeStart mit ui-router stornieren 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();
          }
       });
    });
6
SonTL

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 IS DIESER CODE?

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

Um das Versprechen zu lösen, brauchen wir jedoch einen Griff auf das "Aufgeschobene". Wir verwenden daher eine Karte, damit wir die "zurückgestellte" später abrufen können. Wenn wir die Abbruchmethode aufrufen, wird die "verzögerte" Methode von der Karte abgerufen und dann die Auflösungsmethode aufgerufen, um die http-Anforderung abzubrechen.

Hoffe das hilft jemandem.

EINSCHRÄNKUNGEN

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

WIE BENUTZT MAN ...

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

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

in dieser Version werden mehrere Anforderungen verarbeitet. Außerdem wird im Rückruf nach dem Status "Abgebrochen" gesucht, um Fehler im Fehlerblock zu unterdrücken. (in TypeScript)

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 durchlaufen.

enter image description here

1
Sonic Soul

Sie können dem Dienst $http Eine benutzerdefinierte Funktion hinzufügen, indem Sie einen "Dekorator" verwenden, der Ihre Versprechen um die Funktion abort() erweitert.

Hier ist ein funktionierender Code:

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

In diesem Code wird die Dekorationsfunktion von anglejs verwendet, um eine with_abort() -Funktion zum Dienst $http Hinzuzufügen.

with_abort() verwendet die Timeout-Option $http, mit der Sie eine http-Anfrage abbrechen können.

Das zurückgegebene Versprechen wurde dahingehend geändert, dass es eine abort() -Funktion enthält. Es enthält auch Code, der sicherstellt, dass abort() 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

Wenn Sie abort() aufrufen, wird die Anforderung standardmäßig abgebrochen und keiner der Promise-Handler ausgeführt.

Wenn Sie möchten, dass Ihre Fehlerbehandlungsroutinen als "wahr" bezeichnet werden, übergeben Sie "wahr" an abort(true).

In Ihrer Fehlerbehandlungsroutine können Sie überprüfen, ob der "Fehler" auf einen "Abbruch" zurückzuführen ist, indem Sie die Eigenschaft xhrStatus ü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;
    });
1
Luis Perez