Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use when then to send file upload sequentially in a function that is also a deferred promise?

I intend to upload an array of files using jQuery.

This intention is wrapped in a function called uploadFilesUsingAjax();

var uploadFilesPromise = uploadFilesUsingAjax();

$.when(uploadFilesPromise).done(function (uploadFilesAjaxResult) {
        // redirect to success page...

I need to wait for all the files to be uploaded successfully before doing something else.

Inside uploadFilesUsingAjax(),

I wrote my code this way

function uploadFilesUsingAjax() {
    var files = pages; // pages is a global variable which is an array of files
    var url = "/users/" + currentUser.id + "/files.json";
    var type = "POST";

    console.info('files length:' + files.length);
    if (files.length > 0) {

        var promises=[];
        for (var i = 0; i < files.length; i++) {
            var data = new FormData();
            var postData = {};
            var file = files.getByIndex(i);
            var key = i + 1;
            if (typeof (file.id) !== "undefined" && file.id > 0) {
                data.append(key, JSON.stringify(file));
            } else {
                data.append(key, file);
            }
            var request = $.ajax({
                //this is the php file that processes the data 
                url: url,

                //POST method is used
                type: type,

                //pass the data
                data: data,

                //Do not cache the page
                cache: false,

                xhr: function() {
                    // custom xhr
                    myXhr = $.ajaxSettings.xhr();
                    if(myXhr.upload) { // check if upload property exists

                            myXhr.upload.addEventListener('progress',updatePagesProgress, false); // for handling the progress of the upload
                    }
                    return myXhr;
                },

                // DO NOT set the contentType and processData
                // see http://stackoverflow.com/a/5976031/80353
                contentType: false,
                processData: false,

                //success
                success: function (json) {
                    // json is already an object thanks to cakephp code to serialize

                    //if POST is a success expect no errors
                    if (json.error == null && json.result != null) {
                        currentUser = json.result.User;
                    // error
                    } else {
                        alert(json.error);
                    }
                }
            });
            promises.push( request);
        }

        var promise = promises[0];
        for (var i = 1; i < promises.length; i++) {
          promise = promise.then(promises[i]);
        }

        return promise.done(function () { console.log('all!')});

Unfortunately, I was not able to upload a lot of files before I got redirected to success page.

I have tried various StackOverflow solutions on how to do this. So far nothing works. Please advise.

Some code has been truncated to save space.

like image 718
Kim Stacks Avatar asked Nov 01 '22 01:11

Kim Stacks


1 Answers

All your promises are parallel and not sequential.

A promise represents an already running task. Promises in JavaScript, unlike C# tasks or other abstractions are already started. The way to represent a task that has not started is a function returning a promise.

Since promises[i] is already a promise - when you do promise.then(object) it does not add a .then handler but rather return immediately. .then ignores any arguments that are not a function.

This is why it returns early, it returns as soon as the first promise fulfills. You also don't need the .when. Create a function that creates an upload process as such:

function createUploadTask(file,i){
    return function(){
         var data = new FormData();
         var postData = {};
         var file = files.getByIndex(i);
         var key = i + 1;
         if (typeof (file.id) !== "undefined" && file.id > 0) {
             data.append(key, JSON.stringify(file));
         } else {
             data.append(key, file);
         }
         return $.ajax({...}); // return the promise
   }
}

Now, you can map the files to tasks:

 var tasks = files.map(createUploadTask);

Note, that now the tasks are each functions that return a promise over a file upload. They are not promises.

Now, you can chain them:

 var p = tasks[0](); // start off the chain
 for(var i = 1; i < tasks.length; i++){
      // chain the next task, note, that we're passing a _function_ here
      // when you return a promise from a `.then` it will fulfill when that promise 
      // fulfills, in our case the $.ajax
      p = p.then(tasks[i]); 
 }
 return p;

You also now don't need to use when, since you return a single promise. I assume you don't need the actual result here (but only to know success/failure).

You simply do:

 function uploadFilesUsingAjax() {
     // settings
     if(tasks.length === 0){
          // a method MUST always either return either synchronously or asynchronously never
          // both, always return a promise. Otherwise you get API hell.
          var d = $.Deferred();
          d.reject(new Error("Called uploadFiles with no files to upload"));
          return d.promise;
     }
     tasks = pages.map(createUploadTask)
     var p = tasks[0](); // call first one
     for(var i = 1; i < tasks.length; i++) p = p.then(tasks[i]);
     return p; 
 }
like image 172
Benjamin Gruenbaum Avatar answered Nov 12 '22 16:11

Benjamin Gruenbaum