Angularjs $ q.all

106

Implementei $ q.all no angularjs, mas não consigo fazer o código funcionar. Aqui está o meu 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);
    }

E aqui está meu controlador que chama os serviços:

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

})

Acho que há algum problema ao configurar $ q.all em meu serviço.

tema 92
fonte
1
Que comportamento você está vendo? Chama a sua atenção then(datas)? Tente apenas pushisto:promises.push(deffered);
Davin Tryon
@ themyth92 você tentou minha solução?
Ilan Frumer
Eu tentei e ambos os métodos funcionam no meu caso. Mas farei com que @Llan Frumer seja a resposta correta. Agradeço muito a vocês dois.
themyth92 de
1
Por que você está prometendo uma promessa existente? $ http já retorna uma promessa. O uso de $ q.defer é supérfluo.
Pete Alvin de
1
É deferrednão deffered:)
Christophe Roussy

Respostas:

225

Em javascript, não existem block-level scopesapenas function-level scopes:

Leia este artigo sobre o escopo e levantamento de javaScript .

Veja como depurei seu 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);
})
  • Quando você escreve var deferred= $q.defer();dentro de um loop for, ele é içado para o topo da função, significa que o javascript declara essa variável no escopo da função fora do for loop.
  • Com cada loop, o último adiado está substituindo o anterior, não há escopo em nível de bloco para salvar uma referência a esse objeto.
  • Quando retornos de chamada assíncronos (sucesso / erro) são invocados, eles fazem referência apenas ao último objeto adiado e apenas ele é resolvido, então $ q.all nunca é resolvido porque ainda espera por outros objetos adiados.
  • O que você precisa é criar uma função anônima para cada item iterado.
  • Como as funções têm escopos, a referência aos objetos adiados é preservada closure scopemesmo após a execução das funções.
  • Como #dfsq comentou: Não há necessidade de construir manualmente um novo objeto adiado, pois o próprio $ http retorna uma promessa.

Solução com angular.forEach:

Aqui está um plunker de demonstração: 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);
}

Minha maneira favorita é usar Array#map:

Aqui está um plunker de demonstração: 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
fonte
14
Boa resposta. Uma adição: não há necessidade de construir um novo adiado, pois o próprio $ http é uma promessa. Portanto, pode ser mais curto: plnkr.co/edit/V3gh7Roez8WWl4NKKrqM?p=preview
dfsq
"Quando você escreve var deferred = $ q.defer (); dentro de um loop for, ele é içado para o topo da função.". Eu não entendo essa parte, você pode explicar o motivo por trás disso?
themyth92
Eu sei, eu faria o mesmo na verdade.
dfsq
4
Adoro o uso de mappara construir uma série de promessas. Muito simples e conciso.
Drumbeg
1
Deve-se observar que a declaração é içada, mas a cessão permanece onde está. Além disso, agora há escopo no nível do bloco com a instrução 'let'. Consulte developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Spencer
36

$ http também é uma promessa, você pode torná-la mais simples:

return $q.all(tasks.map(function(d){
        return $http.post('upload/tasks',d).then(someProcessCallback, onErrorCallback);
    }));
Zerkotin
fonte
2
Sim, a resposta aceita é apenas uma aglomeração de antipadrões.
Roamer-1888 de
Acho que você pode deixar a .then()cláusula de fora, pois o OP quer fazer tudo isso em seu controlador, mas o princípio está totalmente correto.
Roamer-1888 de
2
claro que você pode omitir a cláusula then, eu a adicionei caso você queira registrar todas as solicitações HTTP, eu sempre as adiciono e uso um retorno de chamada onError global, para lidar com todas as exceções do servidor.
Zerkotin
1
@Zerkotin você pode fazer a throwpartir de um .thenpara tanto lidar com isso mais tarde quanto para expô-lo $exceptionHandler, o que deve lhe poupar esse problema e ser global.
Benjamin Gruenbaum
Agradável. Esta é essencialmente a mesma abordagem da última solução / exemplo da resposta aceita.
Niko Bellic
12

O problema parece ser que você está adicionando o deffered.promisequando defferedé a promessa que você deve adicionar:

Tente mudar para promises.push(deffered);para não adicionar a promessa desembrulhada ao array.

 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
fonte
Isso só retorna uma matriz de objetos adiados, eu verifiquei.
Ilan Frumer
Não sei o que ele diz, apenas o que o console diz, você pode ver que não está funcionando: plnkr.co/edit/J1ErNncNsclf3aU86D7Z?p=preview
Ilan Frumer
4
Também a documentação diz claramente que $q.allrecebe promessas e não objetos adiados. O verdadeiro problema do OP é com o escopo e porque apenas o último adiado está sendo resolvido
Ilan Frumer
Ilan, obrigado por desembaraçar deferobjetos e promises. Você consertou meu all()problema também.
Ross Rogers
o problema foi resolvido em 2 respostas, o problema é escopo ou levantamento variável, como você quiser chamá-lo.
Zerkotin de