Pouvez-vous résoudre une promesse angularjs avant de la renvoyer?

125

J'essaye d'écrire une fonction qui renvoie une promesse. Mais il y a des moments où les informations demandées sont disponibles immédiatement. Je veux l'envelopper dans une promesse pour que le consommateur n'ait pas besoin de prendre une décision.

function getSomething(id) {
    if (Cache[id]) {
        var deferred = $q.defer();
        deferred.resolve(Cache[id]); // <-- Can I do this?
        return deferred.promise;
    } else {
        return $http.get('/someUrl', {id:id});
    }
}

Et utilisez-le comme ceci:

somethingService.getSomething(5).then(function(thing) {
    alert(thing);
});

Le problème est que le rappel ne s'exécute pas pour la promesse pré-résolue. Est-ce une chose légitime à faire? Existe-t-il une meilleure façon de gérer cette situation?

Craig Celeste
la source
10
Une manière plus simple d'écrire le retour dans le premier cas est return $q.when(Cache[id]). Quoi qu'il en soit, cela devrait fonctionner et appeler le rappel à chaque fois puisque vous créez de nouvelles promesses à chaque fois.
musicalement_ut
1
Crud. Une heure de ma vie perdue. J'essayais cela dans un test unitaire et la promesse est remplie une fois le test terminé, et je ne le voyais pas. Problème avec mon test et pas avec le code.
Craig Celeste
Assurez-vous d'appeler $ scope. $ Apply () pour vous assurer que les choses se résolvent immédiatement pendant les tests.
dtabuenc
Je pense que httpbackend.flush explique cela mais $ q pourrait ne pas l'être. Je n'utilise pas de portée dans ce test. Je teste le service directement, mais je l'ai fait fonctionner de toute façon, merci.
Craig Celeste

Réponses:

174

Réponse courte: Oui, vous pouvez résoudre une promesse AngularJS avant de la renvoyer, et elle se comportera comme prévu.

De Plunkr de JB Nizet mais remanié pour fonctionner dans le contexte de ce qui était initialement demandé (c'est-à-dire un appel de fonction au service) et effectivement sur site.

À l'intérieur du service ...

function getSomething(id) {
    // There will always be a promise so always declare it.
    var deferred = $q.defer();
    if (Cache[id]) {
        // Resolve the deferred $q object before returning the promise
        deferred.resolve(Cache[id]); 
        return deferred.promise;
    } 
    // else- not in cache 
    $http.get('/someUrl', {id:id}).success(function(data){
        // Store your data or what ever.... 
        // Then resolve
        deferred.resolve(data);               
    }).error(function(data, status, headers, config) {
        deferred.reject("Error: request returned status " + status); 
    });
    return deferred.promise;

}

À l'intérieur du contrôleur ...

somethingService.getSomething(5).then(    
    function(thing) {     // On success
        alert(thing);
    },
    function(message) {   // On failure
        alert(message);
    }
);

J'espère que cela aide quelqu'un. Je n'ai pas trouvé les autres réponses très claires.

h. manteau
la source
2
Je ne peux pas décrire avec des mots à quel point je suis heureux, vous m'avez sauvé tellement de temps h.coates!
rilar
En cas d'échec de http GET, la promesse retournée n'est pas rejetée de cette manière.
lex82 du
5
Donc, le tl; dr pour ce message est: Oui, vous pouvez résoudre une promesse avant de la renvoyer, et elle court-circuitera comme prévu.
ray
1
Cette réponse s'applique également au Q de Kris Kowal sur lequel les promesses d'Angular sont basées.
Keith
J'ai ajouté un exemple de gestion des erreurs à votre réponse, j'espère que ça va.
Simon East
98

Comment renvoyer simplement une promesse pré-résolue dans Angular 1.x

Promesse résolue:

return $q.when( someValue );    // angular 1.2+
return $q.resolve( someValue ); // angular 1.4+, alias to `when` to match ES6

Promesse rejetée:

return $q.reject( someValue );
Andrey Mikhaylov - Lolmaus
la source
1
Pas besoin de cette usine, ces fonctions d'assistance sont déjà disponibles:{resolved: $q.when, rejected: $q.reject}
Bergi
Salut Bergi, merci pour votre précieuse contribution. J'ai modifié ma réponse en conséquence.
Andrey Mikhaylov - lolmaus
2
Je pense que cette réponse devrait être choisie.
Morteza Tourani
@mortezaT S'il était sélectionné, cela ne me rapporterait pas un badge en or. ;)
Andrey Mikhaylov - lolmaus
6

Voici comment je le fais généralement si je veux mettre en cache des données dans un tableau ou un objet

app.factory('DataService', function($q, $http) {
  var cache = {};
  var service= {       
    getData: function(id, callback) {
      var deffered = $q.defer();
      if (cache[id]) {         
        deffered.resolve(cache[id])
      } else {            
        $http.get('data.json').then(function(res) {
          cache[id] = res.data;              
          deffered.resolve(cache[id])
        })
      }
      return deffered.promise.then(callback)
    }
  }

  return service

})

DEMO

charlietfl
la source
0

Vous avez oublié d'initialiser l'élément Cache

function getSomething(id) {
    if (Cache[id]) {
        var deferred = $q.defer();
        deferred.resolve(Cache[id]); // <-- Can I do this?
        return deferred.promise;
    } else {
        Cache[id] = $http.get('/someUrl', {id:id});
        return Cache[id];
    }
}
zs2020
la source
Désolé. C'est vrai. J'essayais de simplifier le code pour plus de clarté dans la question. Même ainsi, s'il entre dans la promesse pré-résolue, il ne semble pas appeler le rappel.
Craig Celeste
2
Je ne pense pas que si vous résolvez une promesse par une promesse, la promesse intérieure est aplatie. Cela remplirait le Cacheavec des promesses au lieu des objets prévus et le type de retour pour les cas où un objet est dans le cache et quand il ne l'est pas ne sera pas le même. C'est plus correct, je pense:$http.get('/someUrl', {id: id}).then(function (response) { Cache[id] = response.data; return Cache[id]; });
musicalement_ut
0

J'aime utiliser une usine pour obtenir les données de ma ressource quelque chose comme.

.factory("SweetFactory", [ "$http", "$q", "$resource", function( $http, $q, $resource ) {
    return $resource("/sweet/app", {}, {
        "put": {
            method: "PUT",
            isArray: false
        },"get": {
            method: "GET",
            isArray: false
        }
    });
}]);

Ensuite, exposez mon modèle dans le service comme celui-ci ici

 .service("SweetService",  [ "$q", "$filter",  "$log", "SweetFactory",
    function ($q, $filter, $log, SweetFactory) {

        var service = this;

        //Object that may be exposed by a controller if desired update using get and put methods provided
        service.stuff={
            //all kinds of stuff
        };

        service.listOfStuff = [
            {value:"", text:"Please Select"},
            {value:"stuff", text:"stuff"}];

        service.getStuff = function () {

            var deferred = $q.defer();

          var promise = SweetFactory.get().$promise.then(
                function (response) {
                    if (response.response.result.code !== "COOL_BABY") {
                        deferred.reject(response);
                    } else {
                        deferred.resolve(response);
                        console.log("stuff is got", service.alerts);
                        return deferred.promise;
                    }

                }
            ).catch(
                function (error) {
                    deferred.reject(error);
                    console.log("failed to get stuff");
                }
            );

            promise.then(function(response){
                //...do some stuff to sett your stuff maybe fancy it up
                service.stuff.formattedStuff = $filter('stuffFormatter')(service.stuff);

            });


            return service.stuff;
        };


        service.putStuff = function () {
            console.log("putting stuff eh", service.stuff);

            //maybe do stuff to your stuff

            AlertsFactory.put(service.stuff).$promise.then(function (response) {
                console.log("yep yep", response.response.code);
                service.getStuff();
            }).catch(function (errorData) {
                alert("Failed to update stuff" + errorData.response.code);
            });

        };

    }]);

Ensuite, mes contrôleurs peuvent l'inclure et l'exposer ou faire ce qu'il pense être juste dans son contexte simplement en référençant le service injecté.

Semble fonctionner bien. Mais je suis un peu nouveau dans angular. * la gestion des erreurs est généralement laissée de côté pour plus de clarté

Frank Swanson
la source
Votre getStuffméthode utilise l'
anti