Angularjs $ q.all


106

Ho implementato $ q.all in angularjs, ma non riesco a far funzionare il codice. Ecco il mio codice:

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

Ed ecco il mio controller che chiama i servizi:

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

})

Penso che ci sia qualche problema nell'impostare $ q.all nel mio servizio.


1
Che comportamento stai vedendo? Ti chiama then(datas)? Prova a fare proprio pushquesto:promises.push(deffered);
Davin Tryon

@ themyth92 hai provato la mia soluzione?
Ilan Frumer

Ho provato ed entrambi i metodi funzionano sul mio caso. Ma userò @Llan Frumer come risposta corretta. Davvero grazie a tutti e due.
themyth92

1
Perché prometti una promessa esistente? $ http restituisce già una promessa. L'uso di $ q.defer è superfluo.
Pete Alvin

1
È deferredNon deffered:)
Christophe Roussy

Risposte:


225

In javascript non ci sono block-level scopessolo function-level scopes:

Leggi questo articolo su javaScript Scoping and Hoisting .

Guarda come ho eseguito il debug del tuo codice:

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 si scrive var deferred= $q.defer();all'interno di un ciclo for è issato in cima alla funzione, significa che javascript dichiara questa variabile sul lato esterno funzione di campo di applicazione del for loop.
  • Con ogni ciclo, l'ultimo posticipato sovrascrive quello precedente, non esiste un ambito a livello di blocco per salvare un riferimento a quell'oggetto.
  • Quando vengono richiamati callback asincroni (successo / errore), fanno riferimento solo all'ultimo oggetto differito e solo esso viene risolto, quindi $ q.all non viene mai risolto perché attende ancora altri oggetti differiti.
  • Quello di cui hai bisogno è creare una funzione anonima per ogni elemento che ripeti.
  • Poiché le funzioni hanno degli ambiti, il riferimento agli oggetti rinviati viene mantenuto in a closure scopeanche dopo che le funzioni sono state eseguite.
  • Come ha commentato #dfsq: non è necessario costruire manualmente un nuovo oggetto differito poiché $ http stesso restituisce una promessa.

Soluzione con angular.forEach:

Ecco un plunker demo: 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);
}

Il mio modo preferito è usare Array#map:

Ecco un plunker demo: 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
Buona risposta. Un'aggiunta: non è necessario costruire un nuovo differito poiché $ http stesso restituisce la promessa. Quindi può essere più breve: plnkr.co/edit/V3gh7Roez8WWl4NKKrqM?p=preview
dfsq

"Quando scrivi var deferred = $ q.defer (); all'interno di un ciclo for, viene posto all'inizio della funzione.". Non capisco questa parte, puoi spiegarci il motivo?
themyth92

Lo so, farei lo stesso in realtà.
dfsq

4
Adoro l'uso di mapper costruire una serie di promesse. Molto semplice e conciso.
Drumbeg

1
Va notato che la dichiarazione è issata, ma l'incarico rimane dov'è. Inoltre, ora è disponibile lo scoping a livello di blocco con l'istruzione "let". Vedi developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Spencer

36

$ http è anche una promessa, puoi renderla più semplice:

return $q.all(tasks.map(function(d){
        return $http.post('upload/tasks',d).then(someProcessCallback, onErrorCallback);
    }));

2
Sì, la risposta accettata è solo un agglomerato di anti-modelli.
Roamer-1888

Penso che si possa tralasciare la .then()clausola poiché l'OP vuole fare tutto ciò nel suo controller, ma il principio è totalmente corretto.
Roamer-1888

2
ovviamente puoi omettere la clausola then, l'ho aggiunta nel caso in cui desideri registrare tutte le richieste HTTP, le aggiungo sempre e utilizzo un callback globale onError, per gestire tutte le eccezioni del server.
Zerkotin

1
@Zerkotin puoi throwda .thena per gestirlo in seguito ed esporlo a $exceptionHandler, il che dovrebbe farti risparmiare quei problemi e un globale.
Benjamin Gruenbaum

Bello. Questo è essenzialmente lo stesso approccio dell'ultima soluzione / esempio della risposta accettata.
Niko Bellic

12

Il problema sembra essere che stai aggiungendo il deffered.promisequando defferedè di per sé la promessa che dovresti aggiungere:

Prova a cambiare in in promises.push(deffered);modo da non aggiungere la promessa scartata all'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);
        }

Questo restituisce solo un array di oggetti differiti, l'ho controllato.
Ilan Frumer

Non so cosa dice, solo cosa dice la console, puoi vedere che non funziona: plnkr.co/edit/J1ErNncNsclf3aU86D7Z?p=preview
Ilan Frumer

4
Anche la documentazione dice chiaramente che $q.allottiene promesse non oggetti differiti. Il vero problema dell'OP è con lo scoping e perché viene risolto solo l'ultimo differito
Ilan Frumer

Ilan, grazie per aver districato gli deferoggetti e promises. Hai risolto anche il mio all()problema.
Ross Rogers

il problema è stato risolto in 2 risposte, il problema è lo scoping o il sollevamento variabile, come vuoi chiamarlo.
Zerkotin
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.