web-dev-qa-db-fra.com

Comment annuler les demandes de ressources $

J'essaie de comprendre comment utiliser la propriété timeout d'une ressource $ pour annuler dynamiquement les demandes en attente. Idéalement, j'aimerais simplement pouvoir annuler les demandes avec certains attributs (en fonction des paramètres envoyés), mais il semble que cela ne soit pas possible. En attendant, j'essaie simplement d'annuler toutes les demandes en attente, puis de réinitialiser la promesse d'expiration pour autoriser les nouvelles demandes.

Le problème semble être que la configuration de $ resource n'autorise qu'une seule promesse statique pour la valeur du délai d'expiration. Il est logique que je puisse faire cela si je faisais des appels $ http individuels, car je pouvais simplement passer de nouvelles promesses pour le délai d'attente, mais comment cela peut-il fonctionner pour une ressource $? J'ai mis en place un exemple de plunker ici: http://plnkr.co/edit/PP2tqDYXh1NAOU3yqCwP?p=preview

Voici mon code de contrôleur:

app.controller('MainCtrl', function($scope, $timeout, $q, $resource) {
  $scope.canceller = $q.defer();
  $scope.pending = 0;
  $scope.actions = [];
  var API = $resource(
    'index.html', {}, {
      get: {
        method: 'GET',
        timeout: $scope.canceller.promise
      }
    }
  )

  $scope.fetchData = function() {
    if ($scope.pending) {
      $scope.abortPending();
    }
    $scope.pending = 1;
    $scope.actions.Push('request');
    API.get({}, function() {
      $scope.actions.Push('completed');
      $scope.pending = 0;
    }, function() {
      $scope.actions.Push('aborted');
    });
  }

  $scope.abortPending = function() {
    $scope.canceller.resolve();
    $scope.canceller = $q.defer();
  }
});

À l'heure actuelle, l'annuleur fonctionne lorsqu'une demande est en attente, mais je ne semble pas être en mesure de la réinitialiser - une fois qu'une demande est abandonnée, toutes les demandes futures seront également abandonnées.

Je suis sûr que je manque quelque chose, car être en mesure d'annuler les demandes en attente semble être une caractéristique assez cruciale de la plupart des applications Web (au moins que j'ai construites).

Merci

27
Greg Michalec

Réponse de Gecko IT fonctionne pour moi, mais j'ai dû faire quelques modifications pour:

  • Permet d'annuler plusieurs fois l'appel de ressource ajax sans avoir besoin de recréer la ressource
  • Rendre les ressources rétrocompatibles - Cela signifie qu'il n'est pas nécessaire de modifier le code d'une application (contrôleur), sauf la fabrique de ressources
  • Rendre le code compatible JSLint

Il s'agit d'une implémentation complète de l'usine de services (il vous suffit de mettre le nom de module approprié):

'use strict';

/**
 * ResourceFactory creates cancelable resources.
 * Work based on: https://stackoverflow.com/a/25448672/1677187
 * which is based on: https://developer.rackspace.com/blog/cancelling-ajax-requests-in-angularjs-applications/
 */
/* global array */
angular.module('module_name').factory('ResourceFactory', ['$q', '$resource',
    function($q, $resource) {

        function abortablePromiseWrap(promise, deferred, outstanding) {
            promise.then(function() {
                deferred.resolve.apply(deferred, arguments);
            });

            promise.catch(function() {
                deferred.reject.apply(deferred, arguments);
            });

            /**
             * Remove from the outstanding array
             * on abort when deferred is rejected
             * and/or promise is resolved/rejected.
             */
            deferred.promise.finally(function() {
                array.remove(outstanding, deferred);
            });
            outstanding.Push(deferred);
        }

        function createResource(url, options, actions) {
            var resource;
            var outstanding = [];
            actions = actions || {};

            Object.keys(actions).forEach(function(action) {
                var canceller = $q.defer();
                actions[action].timeout = canceller.promise;
                actions[action].Canceller = canceller;
            });

            resource = $resource(url, options, actions);

            Object.keys(actions).forEach(function(action) {
                var method = resource[action];

                resource[action] = function() {
                    var deferred = $q.defer(),
                    promise = method.apply(null, arguments).$promise;

                    abortablePromiseWrap(promise, deferred, outstanding);

                    return {
                        $promise: deferred.promise,

                        abort: function() {
                            deferred.reject('Aborted');
                        },
                        cancel: function() {
                            actions[action].Canceller.resolve('Call cancelled');

                            // Recreate canceler so that request can be executed again
                            var canceller = $q.defer();
                            actions[action].timeout = canceller.promise;
                            actions[action].Canceller = canceller;
                        }
                    };
                };
            });

            /**
             * Abort all the outstanding requests on
             * this $resource. Calls promise.reject() on outstanding [].
             */
            resource.abortAll = function() {
                for (var i = 0; i < outstanding.length; i++) {
                    outstanding[i].reject('Aborted all');
                }
                outstanding = [];
            };

            return resource;
        }

        return {
            createResource: function (url, options, actions) {
                return createResource(url, options, actions);
            }
        };
    }
]);

L'utilisation est la même que dans l'exemple Gecko IT. Usine de service:

'use strict';

angular.module('module_name').factory('YourResourceServiceName', ['ResourceFactory', function(ResourceFactory) {
    return ResourceFactory.createResource('some/api/path/:id', { id: '@id' }, {
        create: {
            method: 'POST'
        },
        update: {
            method: 'PUT'
        }
    });
}]);

Utilisation dans le contrôleur (rétrocompatible):

var result = YourResourceServiceName.create(data);
result.$promise.then(function success(data, responseHeaders) {
    // Successfully obtained data
}, function error(httpResponse) {
    if (httpResponse.status === 0 && httpResponse.data === null) { 
        // Request has been canceled
    } else { 
        // Server error 
    }
});
result.cancel(); // Cancels XHR request

Approche alternative:

var result = YourResourceServiceName.create(data);
result.$promise.then(function success(data, responseHeaders) {       
    // Successfully obtained data
}).catch(function (httpResponse) {
    if (httpResponse.status === 0 && httpResponse.data === null) { 
        // Request has been canceled
    } else { 
        // Server error 
    }
});
result.cancel(); // Cancels XHR request

Améliorations supplémentaires:

  • Je n'aime pas vérifier si la demande a été annulée. Une meilleure approche serait d'attacher l'attribut httpResponse.isCanceled lorsque la demande est annulée, et similaire pour l'abandon.
10
Nikola M.

(pour Angular 1.2.28+) Bonjour à tous, je voulais simplement que cela soit facile à comprendre, comment j'ai géré ce problème comme suit:

Ici, je déclare le paramètre de délai d'expiration

$scope.stopRequestGetAllQuestions=$q.defer();

puis je l'utilise comme suit

return $resource(urlToGet, {}, {get:{ timeout: stopRequestGetAllQuestions.promise }});

et si je veux arrêter les appels de ressources $ précédents, je résous simplement cet objet stopRequestGetAllQuestions qui est tout.

stopRequestGetAllQuestions.resolve();

mais si je veux arrêter les précédents et démarrer un nouvel appel à $ resource alors je le fais après stopRequestGetAllQuestions.resolve();:

stopRequestGetAllQuestions = $q.defer(); 
2
katmanco

Il existe actuellement de nombreux exemples. Les deux suivants, j'ai trouvé assez informatif:

Celui-ci montre un exemple comment traiter à la fois les demandes $ resource et $ http: https://developer.rackspace.com/blog/cancelling-ajax-requests-in-angularjs-applications/

et

Celui-ci est plus simple et ne concerne que $ http: http://odetocode.com/blogs/scott/archive/2014/04/24/canceling-http-requests-in-angularjs.aspx

1
Tsonev

Salut, j'ai créé un gestionnaire personnalisé basé sur https: //developer.rackspace.com/blog/...

.factory('ResourceFactory', ["$q", "$resource", function($q, $resource) {
    function createResource(url, options, actions) {
        var actions = actions || {},
        resource,
        outstanding = [];

        Object.keys(actions).forEach(function (action) {
            console.log(actions[action]);
            var canceller = $q.defer();
            actions[action].timeout = canceller.promise;
            actions[action].Canceller = canceller;
        });

        resource = $resource(url, options, actions);

        Object.keys(actions).forEach(function (action) {
            var method = resource[action];

            resource[action] = function () {
                var deferred = $q.defer(),
                promise = method.apply(null, arguments).$promise;

                abortablePromiseWrap(promise, deferred, outstanding);

                return {
                    promise: deferred.promise,

                    abort: function () {
                        deferred.reject('Aborted');
                    },
                    cancel: function () {
                        console.log(actions[action]);
                        actions[action].Canceller.resolve("Call cancelled");
                    }
                };
            };
        });

        /**
        * Abort all the outstanding requests on
        * this $resource. Calls promise.reject() on outstanding [].
        */
        resource.abortAll = function () {
            for (var i = 0; i < outstanding.length; i++) {
                outstanding[i].reject('Aborted all');
            }
            outstanding = [];
        };

        return resource;
    }

    return {
        createResource: function (url, options, actions) {
            return createResource(url, options, actions);
        }
    }
}])

function abortablePromiseWrap(promise, deferred, outstanding) {
    promise.then(function () {
        deferred.resolve.apply(deferred, arguments);
    });

    promise.catch(function () {
        deferred.reject.apply(deferred, arguments);
    });
    /**
    * Remove from the outstanding array
    * on abort when deferred is rejected
    * and/or promise is resolved/rejected.
    */
    deferred.promise.finally(function () {
        array.remove(outstanding, deferred);
    });
    outstanding.Push(deferred);
}


//Usage SERVICE
factory("ServiceFactory", ["apiBasePath", "$resource", "ResourceFactory", function (apiBasePath, $resource, QiteResourceFactory) {
    return ResourceFactory.createResource(apiBasePath + "service/:id", { id: '@id' }, null);
}])

//Usage Controller
var result = ServiceFactory.get();
console.log(result);
result.promise.then(function (data) {       
    $scope.services = data;
}).catch(function (a) {
    console.log("catch", a);
})
//Actually cancels xhr request
result.cancel();
1
Gecko IT

Une solution consiste à recréer la ressource chaque fois que vous en avez besoin.

// for canceling an ajax request
$scope.canceler = $q.defer();

// create a resource
// (we have to re-craete it every time because this is the only
// way to renew the promise)
function getAPI(promise) {
    return $resource(
        'index.html', {}, {
            get: {
                method: 'GET',
                timeout: promise
            }
        }
    );
}

$scope.fetchData = function() {

    // abort previous requests if they are still running
    $scope.canceler.resolve();

    // create a new canceler
    $scope.canceler = $q.defer();

    // instead of using "API.get" we use "getAPI().get"
    getAPI( $scope.canceler.promise ).get({}, function() {
        $scope.actions.Push('completed');
        $scope.pending = 0;
    }, function() {
        $scope.actions.Push('aborted');
    });

}
0
Hendrik Jan

Dans notre tentative de résoudre cette tâche, nous sommes arrivés à la solution suivante:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Cancel resource</title>
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular.js"></script>
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular-resource.js"></script>
  <script>
angular.module("app", ["ngResource"]).
factory(
  "services",
  ["$resource", function($resource)
  {
    function resolveAction(resolve)
    {
      if (this.params)
      {
        this.timeout = this.params.timeout;
        this.params.timeout = null;
      }

      this.then = null;
      resolve(this);
    }

    return $resource(
      "http://md5.jsontest.com/",
      {},
      {
        MD5:
        {
          method: "GET",
          params: { text: null },
          then: resolveAction
        },
      });
  }]).
controller(
  "Test",
  ["services", "$q", "$timeout", function(services, $q, $timeout)
  {
    this.value = "Sample text";
    this.requestTimeout = 100;

    this.call = function()
    {
      var self = this;

      self.result = services.MD5(
      {
        text: self.value,
        timeout: $q(function(resolve)
        {
          $timeout(resolve, self.requestTimeout);
        })
      });
    }
  }]);
  </script>
</head>
<body ng-app="app" ng-controller="Test as test">
  <label>Text: <input type="text" ng-model="test.value" /></label><br/>
  <label>Timeout: <input type="text" ng-model="test.requestTimeout" /></label><br/>
  <input type="button" value="call" ng-click="test.call()"/>
  <div ng-bind="test.result.md5"></div>
</body>
</html>

Comment ça fonctionne

  1. $ resource fusionne la définition d'action, les paramètres de requête et les données pour créer un paramètre de configuration pour une requête $ http.
  2. un paramètre de configuration passé dans une requête $ http est traité comme un objet de type promesse, il peut donc contenir ensuite une fonction pour initialiser la configuration.
  3. la fonction puis de l'action peut passer la promesse de délai d'expiration des paramètres dans la configuration.

Veuillez consulter " Annuler la demande de ressources Angularjs " pour plus de détails.

0