Несколько асинхронных вызовов в AngularJS с использованием результата

В приложении AngularJS, которое я разрабатываю, я пытаюсь асинхронно обработать несколько загруженных файлов, чтобы подготовить их к подпрограмме, которая добавляет их в экземпляр SuiteCRM. В целом код не содержит ошибок, но я обнаружил, что отложенные вызовы выполняются один за другим и не передают результат ожидающему сценарию до тех пор, пока не будут выполнены все промисы. Возможно, кто-то может помочь мне с тем, как связать их, чтобы получить результат, который я хочу?

  function fileReader(file) { // perform async operation
      var deferred = $q.defer();
      var reader = new FileReader();
      reader.onload = function () {
          deferred.resolve(reader.result.replace(/^(.*),/g, ""));
      };
      reader.readAsDataURL(file);
      return deferred.promise;
  }

  $scope.uploadFiles = function () {
        console.log('Starting uploads');
        $scope.uploadClicked = true;
        $scope.uploadProgress = {};
        $applicationID = $('#application_id').val();
        var tmpMerchData = SessionService.getMerchantData();
        var isoID = SessionService.getISOID();
        if ($scope.fileList && $scope.fileList.length) {
            var $myFiles = $scope.fileList;
            for (var f = 0; f < $myFiles.length; f++) {
                var thisFile = $myFiles[f];
                console.debug(thisFile);
                fileReader(thisFile.file).then(function (result) {
                    ApplicationFactory.createNewUploadedDocument(thisFile, thisFile.templatetype, result, thisFile.description, $scope.userID, tmpMerchData.id, $applicationID, isoID).then(
                            function successCallback(response) {
                                if (response.data.status === 1) {
                                    console.log("Document creation succeeded");
                                    $scope.uploadProgress = {
                                        'progress': 100,
                                        'index': f
                                    };
                                } else {
                                    console.log("Document creation failed");
                                }
                            }, function errorCallback(response) {
                        console.debug("Error returned from createNewUploadedDocument", response);
                    });
                });
            }
        }
    };

Итак, вы видите, мне нужно, чтобы FileReader обрабатывал файл, возвращал содержимое файла, чтобы передать его функции createNewUploadedDocument для каждого из f (файлов) в моем $scope.fileList. Параметр нескольких файлов NGF недоступен для меня, потому что каждый из этих файлов имеет определенный тип шаблона и описание.

Любая помощь, которую вы можете предоставить или предложить, будет принята с благодарностью.

EDIT - я прочитал это Объединение нескольких промисов, созданных с помощью цикла for, и этого angular $q, Как связать несколько промисов внутри и после цикла for, но ни один из них не дает полезной обратной связи для моей конкретной ситуации. Я понимаю, что промисы могут быть вложены и объединены с помощью $q.all, но могут ли они по-прежнему передавать полученные данные друг другу? Мой внутренний метод - это вызов REST API - не то, чтобы это имело какое-либо значение, но мне нужен результат обещания FileReader, чтобы вызвать его.


person DevlshOne    schedule 25.01.2017    source источник
comment
Какая часть не работает? Код немного запутан, но должен работать, но, поскольку вы не совсем ясно представляете, каков желаемый результат, вам трудно помочь.   -  person Daniel B    schedule 25.01.2017
comment
Он перебирает все промисы fileReader перед вызовом базовой функции.   -  person DevlshOne    schedule 25.01.2017
comment
Это невозможно. Он должен продолжаться до первого .then(), когда первое обещание разрешается из fileReader. Ничто не мешает ему продолжать, поэтому тот случай, когда он будет ждать всех промисов перед вводом любого из .then(), просто нелогичен.   -  person Daniel B    schedule 25.01.2017
comment
Я получил консольный вывод из этого, и он делает именно это - перебирает каждый fileReader, а затем предоставляет только последний result для .then(). Как бы нелогично это ни казалось (и я думал, что код должен работать так, как он написан)!   -  person DevlshOne    schedule 25.01.2017


Ответы (2)


ОТВЕТ – эта обновленная версия исходного кода устраняет проблему.

Обратите внимание, что был реализован angular.forEach, а также некоторые очищенные имена переменных и их повторное использование.

    $scope.uploadFiles = function () {
        console.log('Starting uploads');
        $scope.uploadClicked = true;
        $scope.uploadProgress = {};
        $applicationID = $('#application_id').val();
        var tmpMerchData = SessionService.getMerchantData();
        var isoID = SessionService.getISOID();
        if ($scope.fileList && $scope.fileList.length) {
            angular.forEach($scope.fileList, function (thisFile, f) {
                console.log('Starting upload #', f);
                fileReader(thisFile.file).then(
                        function (fileContents) {
                            ApplicationFactory.createNewUploadedDocument(thisFile, thisFile.templatetype, fileContents, thisFile.description, $scope.userID, tmpMerchData.id, $applicationID, isoID).then(
                                    function successCallBack(response) {
                                        if (response.data.status === 1) {
                                            console.log('Document creation succeeded');
                                            $scope.uploadProgress = {
                                                'progress': 100,
                                                'index': f
                                            };
                                        } else {
                                            console.log('Document creation failed');
                                        }
                                    }, function errorCallback(response) {
                                console.debug('Error returned from createNewUploadedDocument ', response);
                            });
                        });
            });
        }
    };
person DevlshOne    schedule 25.01.2017
comment
Хотя f теперь находится в ловушке закрытия, $scope.uploadProgress, вероятно, (в зависимости от его природы) по-прежнему будет ненадежным индикатором фактического прогресса. Поскольку ApplicationFactory.createNewUploadedDocument() является асинхронным, нет гарантии, что его ответы вернутся в исходном порядке. Если это индикатор в виде термометра, он может прыгать повсюду и заканчиваться где-то, кроме 100%. - person Roamer-1888; 26.01.2017
comment
Поэтому вместо 'index': f используйте 'index': ++count, где var count = 0 объявляется вне цикла $scope.fileList.forEach(). После этого вам больше не нужно закрытие, поэтому вы можете вернуться к циклу for. - person Roamer-1888; 26.01.2017
comment
На самом деле, я не полагаюсь на то, что $scope.uploadProgress является фактическим индикатором процесса загрузки, он сразу переходит на 100%, как только файл готов. Однако я понимаю, что вы имеете в виду. - person DevlshOne; 27.01.2017
comment
Хорошо, как я и опасался, я многого не понимаю в дизайне. Удачи с этим. - person Roamer-1888; 28.01.2017

Вы можете эффективно использовать сервис $q. Когда вы используете $q.all(), вы передаете массив промисов и в результате получаете массив результатов каждого промиса в том же порядке. Итак, мы можем переписать ваш код следующим образом:

if ($scope.fileList && $scope.fileList.length) {
   var $myFiles = $scope.fileList;
   var promisesFileReader = [];
   var promisesUploadedDocument = [];
   for (var f = 0; f < $myFiles.length; f++) {
      var thisFile = $myFiles[f];
      console.debug(thisFile);
      promisesFileReader.push(fileReader(thisFile.file));
   }

   $q.all(promisesFileReader)
      .then(function(results) {

         //We loop on results and push all the UploadDocument promises with the correct result 
         for (var i = 0; i < results.length; i++) {
            promisesUploadedDocument.push(ApplicationFactory.createNewUploadedDocument(thisFile, thisFile.templatetype, results[i], thisFile.description, $scope.userID, tmpMerchData.id, $applicationID, isoID));
         }

         $q.all(promisesUploadedDocument)
            .then(
               function successCallback(response) {
                  if (response.data.status === 1) {
                     console.log("Document creation succeeded");
                     $scope.uploadProgress = {
                        'progress': 100,
                        'index': f
                     };
                  } else {
                     console.log("Document creation failed");
                  }
            },
              function errorCallback(response) {
                 console.debug("Error returned from createNewUploadedDocument", response);
            });
       });
}
person Emeline Esteves    schedule 25.01.2017
comment
Это не сработает, поскольку вы вызываете $q.all каждую итерацию в цикле for. Вы должны переместить $q.all за пределы цикла и вызвать его один раз, когда у вас будет полный массив промисов из fileReader. - person Daniel B; 25.01.2017
comment
Извините, это не работает. Он возвращает результаты f раз. Каждый раз как увеличивающийся более крупный объект. - person DevlshOne; 25.01.2017
comment
Мне жаль. Вы должны убрать запросы $q.all из цикла for. Циклы позволяют помещать все обещания в массив, и после этого вы можете вызывать каждое обещание с помощью $q.all. - person Emeline Esteves; 27.01.2017