3

I have an array of images which I iterate and upload each to a remote server. Once the file is uploaded I use the result to get the file name and push to another array.

The problem I'm having is that not all results are being pushed to the array. I'm trying to construct an array with the results for all images uploaded. The following code executes lastTask before the forEach loop is finished:

    /**
     * Upload picture
     */
    $scope.upload = function () {

      var token = window.localStorage.getItem('yourTokenKey');
      var defer = $q.defer();
      var promises = [];

      var options = {
        fileKey: "image",
        fileName: " test.png",
        chunkedMode: true,
        mimeType: "image/png",
        headers: {'x-auth-token': token}
      };

      function lastTask() {
        alert(promises);
        $scope.images = [];
        $scope.hide();
        $scope.showAlert();
        $scope.putQc();
        alert('finish').then(function () {
          defer.resolve();
        });
      }

      angular.forEach($scope.images, function (image) {
        $cordovaFileTransfer.upload("http://server.com/file/upload", image, options).then(function (result) {
          // Success!
          promises.push(result.response + '.jpg');
          alert(JSON.stringify(result));
          console.log("SUCCESS: " + JSON.stringify(result.response));
          // Error
        }, function (err) {
          alert(JSON.stringify(err));
          console.log("ERROR: " + JSON.stringify(err));
          // constant progress updates
        }, function (progress) {
          $scope.show();
        });
      });

      $q.all(promises).then(lastTask);

      return defer;
};
mido
  • 24,198
  • 15
  • 92
  • 117
fpena06
  • 2,246
  • 3
  • 20
  • 28

2 Answers2

3

If I am correct, your promises array is the problem, instead of pushing the promises coming from $cordovaFileTransfer.upload you are pushing the result string that you are getting.

Second issue, I think what you are doing is promise anti-pattern, no need for $q.defer() here.

(third, might be irrelevent, JSON.stringify not really needed, and hoping that alert is defined by you)

I would re-write it as:

/**
 * Upload picture
 */
$scope.upload = function () {

  var token = window.localStorage.getItem('yourTokenKey');
  var promises = [], results;   //CHANGED

  var options = {
    fileKey: "image",
    fileName: " test.png",
    chunkedMode: true,
    mimeType: "image/png",
    headers: {'x-auth-token': token}
  };

  function lastTask() {
    alert(results);   //CHANGED
    $scope.images = [];
    $scope.hide();
    $scope.showAlert();
    $scope.putQc();
    return alert('finish');   //CHANGED
  }

  promises = $scope.images.map(function (image) {   //CHANGED
    return $cordovaFileTransfer.upload("http://server.com/file/upload", image, options).then(function (result) {   //CHANGED
      // Success!
      results.push(result.response + '.jpg');   //CHANGED
      alert(JSON.stringify(result));
      console.log("SUCCESS: " + JSON.stringify(result.response));
      // Error
    }, function (err) {
      alert(JSON.stringify(err));
      console.log("ERROR: " + JSON.stringify(err));
      throw err;
      // constant progress updates
    }, function (progress) {
      $scope.show();
    });
  });

  return $q.all(promises).then(lastTask);   //CHANGED

};
Community
  • 1
  • 1
mido
  • 24,198
  • 15
  • 92
  • 117
  • 1
    If you keep the error function in there, you should re-throw the error so `q.all` fails properly. Otherwise your error handler runs, and the promise chain becomes a resolved promise (with the resolve value being `undefined` — the return value of the error handler). – Gabriel L. Jun 17 '15 at 04:07
  • not sure why but this stays on $scope.show(); and never runs lastTask() – fpena06 Jun 17 '15 at 05:05
  • might be beacuse all the promises are not resolving...can you check if all the images are uploaded but still lastTask not being called? – mido Jun 17 '15 at 07:07
  • all images are being uploaded. just never performs lastTask. – fpena06 Jun 17 '15 at 18:37
  • @fpena06 add a error listener t0 `$q.all(..`, may be some error is occuring and it is not seen... – mido Jun 18 '15 at 02:11
2

The problem is that you are not pushing promises into your promise array. You are (eventually) pushing normal values into the array, but not before you've run a $q.all on an empty or near-empty array.

$scope.upload = function() {

    var token = window.localStorage.getItem('yourTokenKey');
    var promises = [];

    var options = {
        fileKey: "image",
        fileName: " test.png",
        chunkedMode: true,
        mimeType: "image/png",
        headers: {
            'x-auth-token': token
        }
    };

    function lastTask(results) {
        alert(results);
        $scope.images = [];
        $scope.hide();
        $scope.showAlert();
        $scope.putQc();
        return results; // keeping the promise chain useful
    }

    angular.forEach($scope.images, function(image) {
        var promiseForUpload = $cordovaFileTransfer.upload("http://server.com/file/upload", image, options).then(function(result) {
            console.log("SUCCESS: " + JSON.stringify(result.response));
            return result.response + '.jpg'; // this will be the promised value
        }, function(err) {
            console.log("ERROR: " + JSON.stringify(err));
            throw err; // re-throwing the error so the q.all promise is rejected
        }, function(progress) {
            $scope.show();
        });
        promises.push(promiseForUpload);
    });

    return $q.all(promises).then(lastTask); // returns a promise for results

};

Honestly though I would get rid of the mid-level stuff altogether:

$scope.upload = function() {

    var token = window.localStorage.getItem('yourTokenKey');

    var options = { /* stuff */ };

    function lastTask(results) { // I wouldn't bake this into the $scope.upload, but do it as a result of calling $scope.upload (i.e. with .then).
        alert(results);
        $scope.images = [];
        $scope.hide();
        $scope.showAlert();
        $scope.putQc();
        return results; // keeping the promise chain useful
    }

    var promises = $scope.images.map(function(image){
        return $cordovaFileTransfer.upload("http://server.com/file/upload", image, options);
    });

    return $q.all(promises).then(lastTask); // returns a promise for results

};

Finally, if this function is supposed to return a promise, then wherever it is used be sure to add error handling (e.g. with .catch at the end of the chain). If this function is supposed to be the end of the promise chain (e.g. triggered just by a button click and nothing is listening to the response), then you need to add the catch to the end of $q.all(promises).then(lastTask). Don't let silent errors occur.

Gabriel L.
  • 1,629
  • 1
  • 17
  • 18