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 в моем сервисе возникла проблема.

Темит92
источник
1
Какое поведение вы наблюдаете? Это вызывает у вас then(datas)? Попробуйте вот pushчто:promises.push(deffered);
Дэвин Трайон
@ themyth92 вы пробовали мое решение?
Илан Фрумер
Я пробовал, и оба метода работают в моем случае. Но я сделаю @Llan Frumer правильным ответом. Спасибо вам обоим.
themyth92
1
Почему вы обещаете существующее обещание? $ http уже возвращает обещание. Использование $ q.defer излишне.
Пит Элвин,
1
Это deferredне так deffered:)
Christophe

Ответы:

225

В javascript нет block-level scopesтолько function-level scopes:

Прочтите эту статью о javaScript Scoping and Hoisting .

Посмотрите, как я отлаживал ваш код:

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.
  • В каждом цикле последний deferred перекрывает предыдущий, нет области уровня блока для сохранения ссылки на этот объект.
  • Когда вызываются асинхронные обратные вызовы (успех / ошибка), они ссылаются только на последний отложенный объект и разрешается только он, поэтому $ 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
Хороший ответ. Одно дополнение: нет необходимости создавать новый deferred, поскольку сам $ http возвращает обещание. Так что это может быть короче: plnkr.co/edit/V3gh7Roez8WWl4NKKrqM?p=preview
dfsq
«Когда вы пишете var deferred = $ q.defer (); внутри цикла for он поднимается в начало функции.». Я не понимаю эту часть, вы можете объяснить причину этого?
themyth92
Я знаю, я бы сделал то же самое.
dfsq
4
Любите использовать mapдля создания множества обещаний. Очень просто и лаконично.
Drumbeg
1
Следует отметить, что объявление поднимается, но присвоение остается там, где оно есть. Кроме того, теперь есть область видимости на уровне блока с помощью оператора let. См. 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
Да, принятый ответ - это просто скопление антипаттернов.
Roamer-1888
Я думаю, вы можете опустить этот .then()пункт, поскольку OP хочет сделать все это в своем контроллере, но принцип полностью верен.
Roamer-1888
2
конечно, вы можете опустить предложение then, я добавил его, если вы хотите регистрировать все HTTP-запросы, я всегда добавляю их и использую глобальный обратный вызов onError для обработки всех исключений сервера.
Зеркотин
1
@Zerkotin вы можете throwиз a .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
Илан Фрумер
4
Также в документации четко сказано, что $q.allполучает обещания, а не отложенные объекты. Настоящая проблема OP - это определение объема, и потому что решается только последний отложенный
Илан Фрумер
Илан, спасибо за распутывание deferпредметов и promises. Вы all()тоже исправили мою проблему.
Росс Роджерс
проблема была решена в двух ответах, проблема заключается в масштабировании или подъеме переменных, как бы вы это ни называли.
Зеркотин