Angularjs $ q.all

106

Implementé $ q.all en angularjs, pero no puedo hacer que el código funcione. Aquí está mi código:

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

Y aquí está mi controlador que llama a los servicios:

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

})

Creo que hay algún problema al configurar $ q.all en mi servicio.

themyth92
fuente
1
¿Qué comportamiento estás viendo? ¿Llama a tu then(datas)? Intenta solo pushesto:promises.push(deffered);
Davin Tryon
@ themyth92 ¿has probado mi solución?
Ilan Frumer
Lo he intentado y ambos métodos funcionan en mi caso. Pero haré que @Llan Frumer sea la respuesta correcta. Realmente gracias a los dos.
themyth92
1
¿Por qué prometes una promesa existente? $ http ya devuelve una promesa. El uso de $ q.defer es superfluo.
Pete Alvin
1
Es deferredno deffered:)
Christophe Roussy

Respuestas:

225

En javascript no block-level scopessolo hay function-level scopes:

Lea este artículo sobre elevación y alcance de javaScript .

Vea cómo depuré su código:

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);
})
  • Cuando escribe var deferred= $q.defer();dentro de un bucle for, se coloca en la parte superior de la función, significa que javascript declara esta variable en el alcance de la función fuera del for loop.
  • Con cada ciclo, el último aplazado reemplaza al anterior, no hay un alcance a nivel de bloque para guardar una referencia a ese objeto.
  • Cuando se invocan devoluciones de llamada asincrónicas (éxito / error), hacen referencia solo al último objeto diferido y solo se resuelve, por lo que $ q.all nunca se resuelve porque todavía espera otros objetos diferidos.
  • Lo que necesita es crear una función anónima para cada elemento que itera.
  • Dado que las funciones tienen ámbitos, la referencia a los objetos diferidos se conserva closure scopeincluso después de que se ejecuten las funciones.
  • Como comentó #dfsq: No es necesario construir manualmente un nuevo objeto diferido ya que $ http en sí mismo devuelve una promesa.

Solución con angular.forEach:

Aquí hay un plunker de demostración: 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);
}

Mi forma favorita es usar Array#map:

Aquí hay un plunker de demostración: 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
fuente
14
Buena respuesta. Una adición: no es necesario construir un nuevo diferido ya que $ http devuelve una promesa. Entonces puede ser más corto: plnkr.co/edit/V3gh7Roez8WWl4NKKrqM?p=preview
dfsq
"Cuando escribe var deferred = $ q.defer (); dentro de un bucle for, se eleva a la parte superior de la función". No entiendo esta parte, ¿puede explicar la razón detrás de esto?
themyth92
Lo sé, yo haría lo mismo en realidad.
dfsq
4
Me encanta el uso de mappara construir una variedad de promesas. Muy simple y conciso.
Drumbeg
1
Cabe señalar que la declaración está izada, pero la asignación se queda donde está. Además, ahora hay un alcance a nivel de bloque con la declaración 'let'. Ver developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Spencer
36

$ http también es una promesa, puedes hacerlo más simple:

return $q.all(tasks.map(function(d){
        return $http.post('upload/tasks',d).then(someProcessCallback, onErrorCallback);
    }));
Zerkotin
fuente
2
Sí, la respuesta aceptada es solo una aglomeración de antipatrones.
Roamer-1888
Creo que puede .then()omitir la cláusula ya que el OP quiere hacer todo eso en su controlador, pero el principio es totalmente correcto.
Roamer-1888
2
por supuesto, puede omitir la cláusula then, la agregué en caso de que desee registrar todas las solicitudes HTTP, siempre las agrego y uso una devolución de llamada global onError, para manejar todas las excepciones del servidor.
Zerkotin
1
@Zerkotin se puede throwpartir de un .thenfin de mango tanto más tarde y lo exponga a $exceptionHandler, que debe guardar ese problema y una global.
Benjamin Gruenbaum
Agradable. Este es esencialmente el mismo enfoque que la última solución / ejemplo de la respuesta aceptada.
Niko Bellic
12

El problema parece ser que está agregando el deffered.promisecuándo defferedes en sí mismo la promesa que debería agregar:

Intente cambiar a promises.push(deffered);para no agregar la promesa no envuelta a la matriz.

 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
fuente
Esto solo devuelve una matriz de objetos diferidos, lo verifiqué.
Ilan Frumer
No sé lo que dice, solo lo que dice la consola, puedes ver que no funciona: plnkr.co/edit/J1ErNncNsclf3aU86D7Z?p=preview
Ilan Frumer
4
También la documentación dice claramente que se $q.allobtienen promesas, no objetos diferidos. El verdadero problema del OP es con el alcance y porque solo se está resolviendo el último aplazado
Ilan Frumer
Ilan, gracias por desenredar deferobjetos y promises. También arreglaste mi all()problema.
Ross Rogers
el problema se resolvió en 2 respuestas, el problema es el alcance o la elevación de variables, como quiera llamarlo.
Zerkotin