Angularjs $ q.all

106

Saya telah menerapkan $ q.all di angularjs, tetapi saya tidak dapat membuat kode berfungsi. Ini kode saya:

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

Dan inilah pengontrol saya yang memanggil layanan:

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

})

Saya rasa ada masalah saat menyiapkan $ q.all di layanan saya.

themyth92
sumber
1
Perilaku apa yang Anda lihat? Apakah itu memanggil Anda then(datas)? Coba lakukan pushini:promises.push(deffered);
Davin Tryon
@ themyth92 sudahkah Anda mencoba solusi saya?
Ilan Frumer
Saya telah mencoba dan kedua metode berfungsi pada kasus saya. Tapi saya akan menjadikan @Llan Frumer sebagai jawaban yang benar. Benar-benar terima kasih kalian berdua.
themyth92
1
Mengapa Anda menjanjikan janji yang ada? $ http sudah mengembalikan janji. Penggunaan $ q.defer tidak berguna.
Pete Alvin
1
Hal ini deferredtidak deffered:)
Christophe Roussy

Jawaban:

225

Di javascript tidak block-level scopeshanya ada function-level scopes:

Baca artikel ini tentang Scoping and Hoisting javaScript .

Lihat bagaimana saya men-debug kode Anda:

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);
})
  • Ketika Anda menulis var deferred= $q.defer();di dalam perulangan for itu diangkat ke atas fungsi, itu berarti bahwa javascript mendeklarasikan variabel ini pada ruang lingkup fungsi di luar for loop.
  • Dengan setiap loop, yang terakhir penangguhan menggantikan yang sebelumnya, tidak ada cakupan tingkat blok untuk menyimpan referensi ke objek itu.
  • Saat callback asinkron (berhasil / error) dipanggil, callback hanya merujuk objek yang ditangguhkan terakhir dan hanya itu yang diselesaikan, jadi $ q.all tidak pernah diselesaikan karena masih menunggu objek lain yang ditangguhkan.
  • Yang Anda butuhkan adalah membuat fungsi anonim untuk setiap item yang Anda iterasi.
  • Karena fungsi memang memiliki cakupan, referensi ke objek yang ditangguhkan dipertahankan di a closure scope bahkan setelah fungsi dijalankan.
  • Seperti komentar #dfsq: Tidak perlu membuat objek baru yang ditangguhkan secara manual karena $ http itu sendiri mengembalikan sebuah promise.

Solusi dengan angular.forEach :

Berikut ini demo plunker: 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);
}

Cara favorit saya adalah menggunakan Array#map :

Berikut ini demo plunker: 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
sumber
14
Jawaban yang bagus. Satu tambahan: tidak perlu membuat deferred baru karena $ http itu sendiri mengembalikan promise. Jadi bisa lebih pendek: plnkr.co/edit/V3gh7Roez8WWl4NKKrqM?p=preview
dfsq
"Saat Anda menulis var deferred = $ q.defer (); di dalam perulangan for, itu akan diangkat ke atas fungsi.". Saya tidak mengerti bagian ini, bisakah Anda menjelaskan alasan di baliknya?
themyth92
Saya tahu, saya akan melakukan hal yang sama.
dfsq
4
Senang menggunakan mapuntuk membangun serangkaian janji. Sangat sederhana dan ringkas.
Drumbeg
1
Perlu dicatat bahwa deklarasi itu diangkat, tetapi tugas tetap di tempatnya. Juga, sekarang ada pelingkupan level blok dengan pernyataan 'let'. Lihat developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Spencer
36

$ http juga merupakan janji, Anda dapat membuatnya lebih sederhana:

return $q.all(tasks.map(function(d){
        return $http.post('upload/tasks',d).then(someProcessCallback, onErrorCallback);
    }));
Zerkotin
sumber
2
Ya, jawaban yang diterima hanyalah aglomerasi anti-pola.
Roamer-1888
Saya pikir Anda dapat meninggalkan .then()klausul karena OP ingin melakukan semua itu di pengontrolnya, tetapi prinsipnya sepenuhnya benar.
Roamer-1888
2
tentu saja Anda dapat menghilangkan klausa then, saya menambahkannya jika Anda ingin mencatat semua permintaan HTTP, saya selalu menambahkannya dan menggunakan callback onError global, untuk menangani semua pengecualian server.
Zerkotin
1
@ Zerkotin Anda dapat throwdari .thenuntuk menanganinya nanti dan memaparkannya $exceptionHandler, yang akan menyelamatkan Anda dari masalah itu dan global.
Benjamin Gruenbaum
Bagus. Ini pada dasarnya adalah pendekatan yang sama dengan solusi / contoh terakhir dari jawaban yang diterima.
Niko Bellic
12

Masalahnya tampaknya Anda menambahkan deffered.promisekapan deffereditu sendiri janji yang harus Anda tambahkan:

Coba ubah menjadi promises.push(deffered);agar Anda tidak menambahkan janji yang belum dibungkus ke 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
sumber
Ini hanya mengembalikan array objek yang ditangguhkan, saya memeriksanya.
Ilan Frumer
Saya tidak tahu apa yang dia katakan, hanya apa yang dikatakan konsol, Anda dapat melihat itu tidak berfungsi: plnkr.co/edit/J1ErNncNsclf3aU86D7Z?p=preview
Ilan Frumer
4
Juga dokumentasinya mengatakan dengan jelas bahwa $q.allmendapat janji bukan objek yang ditangguhkan. Masalah sebenarnya dari OP adalah dengan pelingkupan dan karena hanya penundaan terakhir yang diselesaikan
Ilan Frumer
Ilan, terima kasih telah menguraikan deferobjek dan promises. Anda memperbaiki all()masalah saya juga.
Ross Rogers
masalah diselesaikan dalam 2 jawaban, masalahnya adalah pelingkupan atau pengangkatan variabel, apa pun Anda ingin menyebutnya.
Zerkotin