Angularjs $ q.all

106

J'ai implémenté $ q.all dans angularjs, mais je ne peux pas faire fonctionner le code. Voici mon code:

UploadService.uploadQuestion = function(questions){

        var promises = [];

        for(var i = 0 ; i < questions.length ; i++){

            var deffered  = $q.defer();
            var question  = questions[i]; 

            $http({

                url   : 'upload/question',
                method: 'POST',
                data  : question
            }).
            success(function(data){
                deffered.resolve(data);
            }).
            error(function(error){
                deffered.reject();
            });

            promises.push(deffered.promise);
        }

        return $q.all(promises);
    }

Et voici mon contrôleur qui appelle les services:

uploadService.uploadQuestion(questions).then(function(datas){

   //the datas can not be retrieved although the server has responded    
}, 
function(errors){ 
   //errors can not be retrieved also

})

Je pense qu'il y a un problème pour configurer $ q.all dans mon service.

themyth92
la source
1
Quel comportement voyez-vous? Cela appelle-t-il votre then(datas)? Essayez juste pushceci:promises.push(deffered);
Davin Tryon
@ themyth92 avez-vous essayé ma solution?
Ilan Frumer
J'ai essayé et les deux méthodes fonctionnent sur mon cas. Mais je ferai de @Llan Frumer la bonne réponse. Vraiment merci à vous deux.
themyth92
1
Pourquoi promettez-vous une promesse existante? $ http renvoie déjà une promesse. L'utilisation de $ q.defer est superflue.
Pete Alvin
1
Ce n'est deferredpas deffered:)
Christophe Roussy

Réponses:

225

En javascript il n'y a pas block-level scopesque function-level scopes:

Lisez cet article sur la portée et le levage javaScript .

Voyez comment j'ai débogué votre code:

var deferred = $q.defer();
deferred.count = i;

console.log(deferred.count); // 0,1,2,3,4,5 --< all deferred objects

// some code

.success(function(data){
   console.log(deferred.count); // 5,5,5,5,5,5 --< only the last deferred object
   deferred.resolve(data);
})
  • Lorsque vous écrivez à l' var deferred= $q.defer();intérieur d'une boucle for, elle est hissée en haut de la fonction, cela signifie que javascript déclare cette variable sur la portée de la fonction en dehors du for loop.
  • Avec chaque boucle, le dernier différé remplace la précédente, il n'y a pas de portée au niveau du bloc pour enregistrer une référence à cet objet.
  • Lorsque des rappels asynchrones (succès / erreur) sont appelés, ils ne font référence qu'au dernier objet différé et seul celui-ci est résolu, donc $ q.all n'est jamais résolu car il attend toujours d'autres objets différés.
  • Ce dont vous avez besoin est de créer une fonction anonyme pour chaque élément que vous itérez.
  • Étant donné que les fonctions ont des portées, la référence aux objets différés est conservée closure scopemême après l'exécution des fonctions.
  • Comme #dfsq l'a commenté: Il n'est pas nécessaire de construire manuellement un nouvel objet différé puisque $ http lui-même renvoie une promesse.

Solution avec angular.forEach:

Voici un plunker de démonstration: http://plnkr.co/edit/NGMp4ycmaCqVOmgohN53?p=preview

UploadService.uploadQuestion = function(questions){

    var promises = [];

    angular.forEach(questions , function(question) {

        var promise = $http({
            url   : 'upload/question',
            method: 'POST',
            data  : question
        });

        promises.push(promise);

    });

    return $q.all(promises);
}

Ma façon préférée est d'utiliser Array#map:

Voici un plunker de démonstration: http://plnkr.co/edit/KYeTWUyxJR4mlU77svw9?p=preview

UploadService.uploadQuestion = function(questions){

    var promises = questions.map(function(question) {

        return $http({
            url   : 'upload/question',
            method: 'POST',
            data  : question
        });

    });

    return $q.all(promises);
}
Ilan Frumer
la source
14
Bonne réponse. Un ajout: pas besoin de construire un nouveau différé puisque $ http lui-même renvoie la promesse. Cela peut donc être plus court: plnkr.co/edit/V3gh7Roez8WWl4NKKrqM?p=preview
dfsq
"Quand vous écrivez var deferred = $ q.defer (); à l'intérieur d'une boucle for, il est hissé en haut de la fonction.". Je ne comprends pas cette partie, pouvez-vous expliquer la raison?
themyth92
Je sais, je ferais la même chose en fait.
dfsq
4
J'adore l'utilisation de mappour construire un éventail de promesses. Très simple et concis.
Drumbeg
1
Il est à noter que la déclaration est hissée, mais l'affectation reste là où elle se trouve. En outre, il existe désormais une portée au niveau du bloc avec l'instruction «let». Voir developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Spencer
36

$ http est aussi une promesse, vous pouvez le rendre plus simple:

return $q.all(tasks.map(function(d){
        return $http.post('upload/tasks',d).then(someProcessCallback, onErrorCallback);
    }));
Zerkotin
la source
2
Oui, la réponse acceptée n'est qu'une agglomération d'anti-modèles.
Roamer-1888
Je pense que vous pouvez laisser l' .then()article de côté car le PO veut faire tout cela dans son contrôleur, mais le principe est tout à fait correct.
Roamer-1888
2
bien sûr, vous pouvez omettre la clause then, je l'ai ajoutée au cas où vous voudriez enregistrer toutes les requêtes HTTP, je les ajoute toujours et j'utilise un rappel global onError, pour gérer toutes les exceptions de serveur.
Zerkotin le
1
@Zerkotin vous pouvez à throwpartir d'un .thenafin de le gérer plus tard et de l' exposer à $exceptionHandler, ce qui devrait vous éviter ce problème et un global.
Benjamin Gruenbaum
Agréable. C'est essentiellement la même approche que la dernière solution / exemple de la réponse acceptée.
Niko Bellic
12

Le problème semble être que vous ajoutez le deffered.promisemomentdeffered est lui-même la promesse que vous devriez ajouter:

Essayez de changer pour promises.push(deffered);afin de ne pas ajouter la promesse non emballée au tableau.

 UploadService.uploadQuestion = function(questions){

            var promises = [];

            for(var i = 0 ; i < questions.length ; i++){

                var deffered  = $q.defer();
                var question  = questions[i]; 

                $http({

                    url   : 'upload/question',
                    method: 'POST',
                    data  : question
                }).
                success(function(data){
                    deffered.resolve(data);
                }).
                error(function(error){
                    deffered.reject();
                });

                promises.push(deffered);
            }

            return $q.all(promises);
        }
Davin Tryon
la source
Cela ne renvoie qu'un tableau d'objets différés, je l'ai vérifié.
Ilan Frumer
Je ne sais pas ce qu'il dit, seulement ce que dit la console, vous pouvez voir que cela ne fonctionne pas: plnkr.co/edit/J1ErNncNsclf3aU86D7Z?p=preview
Ilan Frumer
4
Aussi la documentation dit clairement que les $q.allpromesses ne sont pas des objets différés. Le vrai problème de l'OP est avec la portée et parce que seul le dernier différé est résolu
Ilan Frumer
Ilan, merci pour le démêlage des deferobjets et promises. Tu as réparé monall() problème.
Ross Rogers
le problème a été résolu en 2 réponses, le problème est la portée ou le levage variable, comme vous voulez l'appeler.
Zerkotin