Angularjs $ q.all

106

Zaimplementowałem $ q.all w angularjs, ale nie mogę sprawić, by kod działał. Oto mój kod:

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

A oto mój kontroler, który wywołuje usługi:

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

})

Myślę, że wystąpił problem z konfiguracją $ q.all w mojej usłudze.

themyth92
źródło
1
Jakie zachowanie widzisz? Czy to woła do twojego then(datas)? Spróbuj po prostu pushtak:promises.push(deffered);
Davin Tryon
@ themyth92 Czy wypróbowałeś moje rozwiązanie?
Ilan Frumer
Próbowałem i obie metody działają w moim przypadku. Ale poprawię odpowiedź na @Llan Frumer. Naprawdę dziękuję wam obojgu.
themyth92
1
Dlaczego obiecujesz istniejącą obietnicę? $ http już zwraca obietnicę. Używanie $ q.defer jest zbędne.
Pete Alvin
1
Tak deferrednie jest deffered:)
Christophe Roussy

Odpowiedzi:

225

W javascript nie ma block-level scopestylkofunction-level scopes :

Przeczytaj ten artykuł o javaScript Scoping and Hoisting .

Zobacz, jak debugowałem Twój kod:

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);
})
  • Kiedy piszesz var deferred= $q.defer();wewnątrz pętli for, jest ona podnoszona na górę funkcji, oznacza to, że javascript deklaruje tę zmienną w zakresie funkcji poza for loop.
  • Z każdą pętlą ostatnia odroczona przesłania poprzednią, nie ma zasięgu na poziomie bloku, w którym można by zapisać odwołanie do tego obiektu.
  • Gdy wywoływane są asynchroniczne wywołania zwrotne (powodzenie / błąd), odwołują się one tylko do ostatniego odroczonego obiektu i tylko on zostanie rozwiązany, więc $ q.all nigdy nie jest rozwiązywane, ponieważ nadal czeka na inne odroczone obiekty.
  • To, czego potrzebujesz, to utworzenie anonimowej funkcji dla każdego iterowanego elementu.
  • Ponieważ funkcje mają zakresy, odniesienia do obiektów odroczonych są zachowywane w pliku closure scope nawet po wykonaniu funkcji.
  • Jak skomentował #dfsq: Nie ma potrzeby ręcznego tworzenia nowego odroczonego obiektu, ponieważ sam $ http zwraca obietnicę.

Rozwiązanie z angular.forEach:

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

Moim ulubionym sposobem jest użycie Array#map :

Oto 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
źródło
14
Dobra odpowiedź. Jeden dodatek: nie ma potrzeby konstruowania nowego odroczonego, ponieważ sam $ http zwraca obietnicę. Więc może być krócej: plnkr.co/edit/V3gh7Roez8WWl4NKKrqM?p=preview
dfsq
"Kiedy piszesz var deferred = $ q.defer (); wewnątrz pętli for jest on przenoszony na początek funkcji.". Nie rozumiem tej części, czy możesz wyjaśnić powód tego?
themyth92
Wiem, właściwie zrobiłbym to samo.
dfsq
4
Uwielbiam używać mapdo tworzenia szeregu obietnic. Bardzo proste i zwięzłe.
Drumbeg
1
Należy zauważyć, że deklaracja została podniesiona, ale cesja pozostaje na swoim miejscu. Ponadto istnieje teraz zakres na poziomie bloku za pomocą instrukcji „let”. Zobacz developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Spencer
36

$ http to również obietnica, możesz to uprościć:

return $q.all(tasks.map(function(d){
        return $http.post('upload/tasks',d).then(someProcessCallback, onErrorCallback);
    }));
Zerkotin
źródło
2
Tak, przyjęta odpowiedź to tylko zlepek anty-wzorców.
Roamer-1888
Myślę, że można pominąć .then()klauzulę, ponieważ PO chce to wszystko zrobić w swoim kontrolerze, ale zasada jest całkowicie poprawna.
Roamer-1888
2
oczywiście możesz pominąć klauzulę then, dodałem ją na wypadek, gdybyś chciał rejestrować wszystkie żądania HTTP, zawsze je dodaję i używam globalnego wywołania zwrotnego onError, aby obsłużyć wszystkie wyjątki serwera.
Zerkotin,
1
@Zerkotin możesz throwod a .then, aby zająć się nim później i ujawnić go $exceptionHandler, co powinno zaoszczędzić ci kłopotów i globalnej.
Benjamin Gruenbaum
Miły. Jest to zasadniczo to samo podejście, co ostatnie rozwiązanie / przykład zaakceptowanej odpowiedzi.
Niko Bellic
12

Wydaje się, że problem polega na tym, że dodajesz deffered.promisekiedydeffered polega na sam jest obietnicą, którą powinieneś dodać:

Spróbuj zmienić na, promises.push(deffered);aby nie dodawać rozpakowanej obietnicy do tablicy.

 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
źródło
To tylko zwraca tablicę odroczonych obiektów, sprawdziłem to.
Ilan Frumer
Nie wiem, co mówi, tylko co mówi konsola, widać, że nie działa: plnkr.co/edit/J1ErNncNsclf3aU86D7Z?p=preview
Ilan Frumer
4
Również dokumentacja wyraźnie mówi, że $q.allotrzymuje obietnice, a nie odroczone obiekty. Prawdziwym problemem PO jest ustalanie zakresu i ponieważ rozwiązuje się tylko ostatni odroczony
Ilan Frumer.
Ilan, dzięki za rozplątanie deferprzedmiotów i promises. Naprawiłeś również mój all()problem.
Ross Rogers
problem został rozwiązany w 2 odpowiedziach, problem dotyczy zakresu lub zmiennego podnoszenia, jakkolwiek chcesz to nazwać.
Zerkotin