refactor
refactor

Reputation: 15044

Angularjs asynchronous calls in a loop

Below is the code I use in my project to allow user to upload file(s). For each file selected , "$scope.upload" function gets invoked asynchronously.

Because these are asynchronous calls , each call does not depend on previous call.

But my requirement is only after success call back function gets executed, then the next upload should happen,because each upload depends on values returned by previous upload.

Please let me know how it can be achieved. Note : $scope.upload is part angular file upload js (https://github.com/danialfarid/ng-file-upload)

$scope.startUploading = function ($files, errFiles, item) {           

        //$files: an array of files selected
        for (var idx = 0; idx < $files.length; idx++) {
            var $file = $files[idx];


            (function (index) {
                $scope.upload[index] = $upload.upload({
                    url: "../UploadCommentAttachment",
                    method: "POST",
                    file: $file, 
                    data: item 
                }).success(function (responseData, status, headers, config) {

                       item.AuditCommentAttachments.push(responseData);
                       item.CommentId = responseData.CommentId;
                       item.AuditId = responseData.AuditId;
                       item.Operation = "EDIT";
                       item.CommentAttachmentID = responseData.CommentAttachmentID;
                    }

                });
            })(idx);

        }  ///-- for loop end         
    } // startuploading end

Upvotes: 1

Views: 3505

Answers (3)

georgeawg
georgeawg

Reputation: 48948

Generic Answer

The way to sequentially execute asynchronous operations is to chain them using the promises derived by .then methods. Values to be used by the subsequent promises should be returned inside the .then method.

function sequentiallyExecute = function (opList) {
    //create initial object
    var passedObject = {};
    passedObject.dataList = [];
    passedObject.response = {};

    //create initial promise
    var promise = $q.when(passedObject);

    //sequentially execute opList
    angular.forEach(opList, function(op) {
        promise.then(function (passedObject) {
            var iPromise = asyncOperation(op, passedObject);
            iPromise.then(function onFulfulled(response) {
                //save response for chaining
                passedObject.response = response;
                //push data to list
                passedObject.dataList.push(response.data);
                //return object for chaining
                return passedObject;
            });
            //return promise for chaining
            return iPromise;
        });
    });

    //return promise to client
    return promise;
};

In client code, retrieve final data.

(sequentiallyExecute(opList)
).then (function onFulfilled(passedObject) {
    $scope.dataList = passedObject.dataList;
}).catch (function onRejected(error) {
    console.error(error);
});

For more information on chaining, see the AngularJS $q Service API Reference -- chaining promises.


Specific Answer

Instead of using the .success method (which is not chainable), use the .then method (which is chainable).

function asychOperation(op, passedObject) {
    var $file = op.$file
    var item = passedObject.item;

    var httpPromise = $upload.upload({
         url: "../UploadCommentAttachment",
                    method: "POST",
                    file: $file, 
                    data: item 
    });

    var derivedPromise = httpPromise.then(function (response) {
        var data = response.data
        passedObject.dataList.push(data);
        var newItem = {};
        newItem.CommentId = data.CommentId;
        newItem.AuditId = data.AuditId;
        newItem.Operation = "EDIT";
        newItem.CommentAttachmentID = data.CommentAttachmentID;
        passedObject.item = newItem;
    });

    var promise = derivedPromise.then (function onFulfilled() {
        //return object for chaining
        return passedObject;
    });

    //return promise for chaining
    return promise;
};

The .success and .error methods of the $http service have been removed from the AngularJS framework. Instead use the .then and .catch methods.

For more information, see

Conclusion

Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises.

It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.1

Upvotes: 4

refactor
refactor

Reputation: 15044

Below code does the required job for me , sequentially execute asynchronous operations , without chaining.

   $scope.startUploading = function ($files, item) 
    {
      var cntr = 0;
      function next() 
      {
          if (cntr < $files.length) 
          {

          $scope.upload[cntr] = $upload.upload({
                                url: "../UploadCommentAttachment",
                                method: "POST",
                                file: $files[cntr],
                                data: item
                            }).then(function (responseData) {                                item.AuditCommentAttachments.push(responseData.data);
                            item.CommentId = responseData.data.CommentId;
                            item.AuditId = responseData.data.AuditId;
                            item.Operation = "EDIT";
                            item.CommentAttachmentID = responseData.data.CommentAttachmentID;
                            cntr++;
                            next();
                        });                    
      }
  }
  next();
}

Upvotes: 2

SadiRubaiyet
SadiRubaiyet

Reputation: 328

I have modified an fiddle from the ng-file-upload to handle posts one by one and also use the responce from the previous post to the current one. Please check my fiddle

Here is the part of javascript code that does the trick

function sequentialFileUploadWithReduce(values) {
    var result = ''
    var dfd = $q.defer();

    dfd.resolve();
    return values.reduce(function(currentValue, nextValue, index, values) {
      return Upload.upload({
        url: "https://angular-file-upload-cors-srv.appspot.com/upload",
        method: "POST",
        data: currentValue
      }).then(function(resp) {
        // file is uploaded successfully
        //debugger;
        result += "Uploading file " + index++;
        console.log("Uploading file " + index);
        nextValue.item = resp;
      }, function(resp) {
        // handle error
      }, function(evt) {
        // progress notify
      });
    }, dfd.promise).then(function(responce) {

      return result;
    });
  }

Hope this helps. Also look at the console and network tab to verify that the solution is working.

Upvotes: 1

Related Questions