Angularjs $ q.all


106

Я реалізував $ q.all у angularjs, але не можу змусити код працювати. Ось мій код:

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

І ось мій контролер, який телефонує до служб:

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

})

Я думаю, є певна проблема з налаштуванням $ q.all у моїй службі.


1
Яку поведінку ви бачите? Це закликає до вас then(datas)? Спробуйте саме pushтак:promises.push(deffered);
Davin Tryon

@ themyth92 Ви спробували моє рішення?
Ілан Фрумер

Я спробував, і обидва способи працюють на моєму випадку. Але я зроблю @Llan Frumer як правильну відповідь. Дуже дякую вам обом.
themyth92

1
Чому ти обіцяєш існуючу обіцянку? $ http вже повертає обіцянку. Використання $ q.defer зайве.
Піт Елвін

1
Це deferredне deffered:)
Крістоф Руссі

Відповіді:


225

У JavaScript не є block-level scopesтільки function-level scopes:

Прочитайте цю статтю про розміщення та піднімання JavaScripta .

Подивіться, як я налагодив ваш код:

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);
})
  • Коли ви пишете var deferred= $q.defer();всередині циклу for для циклу, він піднімається до верхньої частини функції, це означає, що javascript оголошує цю змінну в області функції поза межами for loop.
  • З кожним циклом останнє відкладене переосмислює попереднє, не існує області блокового рівня для збереження посилання на цей об'єкт.
  • Коли асинхронні зворотні виклики (успіх / помилка) викликаються, вони посилаються лише на останній відкладений об'єкт, і тільки він стає вирішеним, тому $ q.all ніколи не вирішується, оскільки він все ще чекає інших відкладених об'єктів.
  • Вам потрібно створити анонімну функцію для кожного елемента, який ви повторите.
  • Оскільки функції мають області застосування, посилання на відкладені об'єкти зберігаються closure scopeнавіть після виконання функцій.
  • Як прокоментував #dfsq: Не потрібно вручну створювати новий відкладений об'єкт, оскільки $ http сам повертає обіцянку.

Рішення з angular.forEach:

Ось демонстраційний викрадник: 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);
}

Мій улюблений спосіб - це використовувати Array#map:

Ось демонстраційний планк: 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
Хороша відповідь. Одне доповнення: не потрібно створювати нові відкладені, оскільки $ http сам повертає обіцянку. Тож воно може бути коротшим: plnkr.co/edit/V3gh7Roez8WWl4NKKrqM?p=preview
dfsq

"Коли ви пишете var deferred = $ q.defer (); всередині a для циклу він піднімається до верхньої частини функції." Я не розумію цю частину, чи можете ви пояснити причину?
themyth92

Я знаю, я би робив те ж саме.
dfsq

4
Любіть використання mapдля створення масиву обіцянок. Дуже просто і стисло.
Драмбег

1
Слід зазначити, що декларація піднімається, але призначення залишається там, де воно є. Крім того, зараз існує визначення рівня на рівні блоків із заявою "нехай". Дивіться developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Спенсер

36

$ http - це теж обіцянка, ви можете зробити це простіше:

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

2
Так, прийнята відповідь - це лише агломерація анти-зразків.
Роумер-1888

Я думаю, ви можете залишити .then()пункт, оскільки ОП хоче все це зробити у своєму контролері, але принцип абсолютно правильний.
Roamer-1888,

2
Звичайно, ви можете опустити тодішній пункт, і я додав його у випадку, якщо ви хочете записати всі HTTP-запити, я завжди додаю їх і використовую глобальний зворотний виклик onError для обробки всіх винятків сервера.
Зеркотін

1
@Zerkotin ви можете throwз .thenтого, щоб потім обробити його та викрити його $exceptionHandler, що повинно врятувати вас від цієї проблеми та глобальної ситуації.
Бенджамін Груенбаум

Приємно. Це по суті той же підхід, що і останнє рішення / приклад прийнятої відповіді.
Ніко Белік

12

Проблема, здається, полягає в тому, що ви додаєте deffered.promiseте deffered, що саме тоді ви обіцяєте додавати:

Спробуйте змінити, щоб promises.push(deffered);ви не додавали розгорнуту обіцянку до масиву.

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

Це тільки повертає масив відкладених об'єктів, я перевірив його.
Ілан Фрумер

Я не знаю, що він говорить, лише те, що говорить консоль. Ви можете бачити, що це не працює: plnkr.co/edit/J1ErNncNsclf3aU86D7Z?p=preview
Ilan Frumer

4
Також у документації чітко сказано, що $q.allотримують обіцянки не відкладені об'єкти. Справжня проблема ОП полягає у визначенні масштабу, і тому, що вирішується лише останні відкладені рішення
Ілан Фрумер

Ілан, дякую за роз'єднання deferоб'єктів і promises. Ви також виправили мою all()проблему.
Росс Роджерс

Проблема була вирішена у двох відповідях, проблема - це масштабне або змінне піднімання, як би ви хотіли його назвати.
Zerkotin
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.