web-dev-qa-db-fra.com

Comment annuler une demande $ http dans AngularJS?

Étant donné une demande Ajax dans AngularJS

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

quel est le moyen le plus efficace d'annuler cette demande si une autre demande est lancée (même backend, paramètres différents par exemple).

183
mpm

Cette fonctionnalité était ajoutée à la version 1.1.5 via un paramètre de délai d'attente:

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

Annuler Angular $ http Ajax avec la propriété timeout ne fonctionne pas dans Angular 1.3.15. Pour ceux qui ne peuvent pas attendre que cela soit corrigé, je partage une solution jQuery Ajax intégrée dans Angular.

La solution implique deux services:

  • HttpService (un wrapper autour de la fonction jQuery Ajax);
  • PendingRequestsService (suit les demandes Ajax en attente/ouvertes)

Voici le service 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);

Le service 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);

Plus tard dans votre service, lorsque vous chargez des données, vous utiliserez HttpService au lieu de $ http:

(function (angular) {

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

        this.getResources = function (params) {

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

        };
    }]);

})(window.angular);

Plus tard dans votre code, vous voudriez charger les données:

(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

L'annulation de requêtes émises avec $http n'est pas prise en charge par la version actuelle d'AngularJS. Il y a un demande d'extraction ouverte pour ajouter cette fonctionnalité, mais ce PR n'a pas encore été examiné. Il n'est donc pas clair si cela va en faire le noyau d'AngularJS.

9

Pour une raison quelconque, config.timeout ne fonctionne pas pour moi. J'ai utilisé cette approche:

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

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

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

Et cancelRequest.resolve () pour annuler. En fait, cela n’annule pas une demande, mais vous n’obtenez pas au moins une réponse inutile.

J'espère que cela t'aides.

6
Aliaksandr Hmyrak

Si vous souhaitez annuler les demandes en attente sur stateChangeStart avec ui-router, vous pouvez utiliser quelque chose comme ceci:

// en service

                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;

// dans la configuration 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();
          }
       });
    });
6
SonTL

Cela améliore la réponse acceptée en décorant le service $ http avec une méthode d'abandon comme suit ...

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

QUE IS CE CODE FAIT-IL?

Pour annuler une demande, vous devez définir un délai d'attente "promesse". Si aucun délai n'est défini pour la requête HTTP, le code ajoute un délai "promesse". (Si un délai d'attente est déjà défini, rien n'est changé).

Cependant, pour résoudre la promesse, nous avons besoin d’un traitement du "différé". Nous utilisons donc une carte pour pouvoir récupérer le "différé" plus tard. Lorsque nous appelons la méthode d'abandon, le "différé" est extrait de la carte, puis nous appelons la méthode de résolution pour annuler la requête http.

J'espère que ça aide quelqu'un.

LIMITES

Actuellement, cela ne fonctionne que pour $ http.get mais vous pouvez ajouter du code pour $ http.post et ainsi de suite.

COMMENT UTILISER ...

Vous pouvez ensuite l'utiliser, par exemple, lors d'un changement d'état, comme suit ...

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

voici une version qui gère plusieurs demandes, vérifie également le statut annulé dans le rappel pour supprimer les erreurs dans le bloc d’erreurs. (en TypeScript)

niveau du contrôleur:

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

dans mon http obtenir:

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

méthodes d'assistance

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

maintenant, en regardant l'onglet réseau, je vois que cela fonctionne parfaitement. J'ai appelé la méthode 4 fois et seul le dernier est passé.

enter image description here

1
Sonic Soul

Vous pouvez ajouter une fonction personnalisée au service $http à l'aide d'un "décorateur" qui ajouterait la fonction abort() à vos promesses.

Voici un code de travail:

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

Ce code utilise la fonctionnalité de décorateur d'angularjs pour ajouter une fonction with_abort() au service $http.

with_abort() utilise $http option de délai d'attente qui vous permet d'abandonner une demande http.

La promesse retournée est modifiée pour inclure une fonction abort(). Il a également un code pour vous assurer que la abort() fonctionne même si vous chaînez des promesses.

Voici un exemple d'utilisation:

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

Par défaut, lorsque vous appelez abort(), la demande est annulée et aucun des gestionnaires de promesses ne s'exécute.

Si vous souhaitez que vos gestionnaires d'erreur soient appelés, transmettez true à abort(true).

Dans votre gestionnaire d'erreurs, vous pouvez vérifier si "l'erreur" est due à un "abandon" en vérifiant la propriété xhrStatus. Voici un exemple:

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