0

I have an ionic (v1) project that uses angular and cordova. I am looping over an array of file names and appending each file data in a FormData object, which has to be uploaded to server.

For reading the file data, Cordova/HTML5 provides some methods which are asynchronous. I am using angular's $q promise for calling these methods.

Then I want to use $q.all to wait till all the promises are resolved and start the upload.

But the promises are never getting resolved and the resolved function block inside $q.all(promises).then is never getting called.

Strangely if I reject the promise instead of resolving it with deferred.reject it calls the error method of $q.all.

How do i resolve the promise?

Here is the code:

//Inside a controller
var promises = [];

for (var key in $scope.rArray) {
      if ($scope.rArray.hasOwnProperty(key)) {
          var deferred = $q.defer();
          var tmpFile = $scope.rArray[key];

          var i  = cordova.file.externalRootDirectory + "/" + tmpFile;

          window.resolveLocalFileSystemURL(i, function(fileEntry) {
              fileEntry.file(function(file) {
                  var reader = new FileReader();
                  reader.onloadend = function(e) {
                      console.log('onloadend callled');
                      var fileBlob = new Blob([this.result], { type:file.type});
                      fd.append('file', fileBlob,file.name);
                      deferred.resolve(fd); //if reject here it is reflected
                      //$rootScope.$apply(). tried this too

                  };
                  reader.readAsArrayBuffer(file);

              }, function(e) {
                  console.log('error getting file', e);
                  deferred.reject(e);
              });
          }, function(e) {
              console.log('Error resolving fs url', e);
              deferred.reject(e);
          });

          promises.push(deferred.promise);
      }
    };

    $q.all(promises).then(function (dataAr) {
      console.log('promises resolved..'); //NEVER CALLED
      var request = new XMLHttpRequest();
      request.open('POST', ENV.baseUrl+"/st/st");
      request.send(fd);
    }, function errorfn(err) {
      console.error(JSON.stringify(err));
    })
avck
  • 3,535
  • 3
  • 26
  • 38
  • 1
    from your description, it looks like not all promises are resolved - which is why q.all never proceeds - when ONE promise rejects, q.all rejects regardless of the state of the other promises – Jaromanda X Apr 27 '17 at 02:35
  • `var deferred = $q.defer();` is the issue ... the value of `deferred` will be the LAST ONE for every `deferred.resolve` call - so only the last one can be resolved – Jaromanda X Apr 27 '17 at 02:37

1 Answers1

1

The issue is that the var deferred is the LAST ONE by the time any of the callbacks for any of the resolveLocalFileSystemURL functions is called

so, only one promise can ever be resolved or rejected

with Promise.all, one rejection is enough to reject the Promise returned by Promise.all - but ALL promises have to resolve for Promise.all to resolve

I originally toyed with the idea of a closure in your code - but using common JS methods seemed a far better solution - Object.keys and Array#map

var promises = Object.keys($scope.rArray).map(function(key) {
    var tmpFile = $scope.rArray[key];
    var deferred = $q.defer();
    var i  = cordova.file.externalRootDirectory + "/" + tmpFile;
    window.resolveLocalFileSystemURL(i, function(fileEntry) {
        fileEntry.file(function(file) {
            var reader = new FileReader();
            reader.onloadend = function(e) {
                console.log('onloadend callled');
                var fileBlob = new Blob([this.result], { type:file.type});
                fd.append('file', fileBlob,file.name);
                deferred.resolve(fd); //if reject here it is reflected
            };
            reader.readAsArrayBuffer(file);
        }, function(e) {
            console.log('error getting file', e);
            deferred.reject(e);
        });
    }, function(e) {
        console.log('Error resolving fs url', e);
        deferred.reject(e);
    });

    return deferred.promise;
});
Jaromanda X
  • 53,868
  • 5
  • 73
  • 87
  • It works! But I am confused. Could you explain the control flow in my code. As you mention it is taking the last deferred always for resolving, shouldn't it take the deferred with which it was called initially. I mean the scope for the async callback should be different? – avck Apr 27 '17 at 15:28