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.


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

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

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

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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.