简体   繁体   中英

Deferred promise based multiple file upload handling with javascript in a for loop to an esp32 with ajax

Hi!

I want to upload multiple files with javascript / jquery.

My backend is an esp32.

Current code is working with only one file, but fails to upload multiple files. It is uploading just half of the files when multiple are in the array.

Here is my code:

var promises = [];
//Gets called onchange
function dragAndDrop(el){
    promises = [];
    // Get all the files from the input element
    var files = $(el).prop('files');
    // Make sure there are files, but it's onchange so it must be.
    if(files){
        // add spinner icon to indicate the upload start to the user
        $("#uploadIndicatorIcon").html( '<i class="fas fa-spinner fa-spin"></i>' );
        // Check if the files fit in the filesystem
        if( !isFileArrFits(files) ){
            debug("error","There is not enough space for the files!");
            uploadingDone();
            return false;
        }
        // iterate trought all the files and upload them one by one.
        for (let file of files) {
            promises.push( uploadFile(file) );
        }
        // register the promises done from the ajax.
        updatePromises();
    }
}

function updatePromises(){
    $.when.apply(null, promises).done(function() {
        if(promises.length > 1){
            $('.file-input').prev().text("Files uploaded.");
            debug("success","Files uploaded.");
        }else{
            $('.file-input').prev().text("File uploaded.");
        }
        setTimeout(() => {uploadingDone();}, 100);
    })
}

// reset everything
function uploadingDone(){
    $("#fileUploadInput").val("");
    $("#uploadIndicatorIcon").empty();
    $('.file-input').prev().text("or drag and drop multiple files or a firmware");
}

function uploadFile(file){
    var url = "/uploading";
    if( file.name.includes("firmware") ){url = "/uploadingfirm"}
    form = new FormData();
    form.append("file", file, file.name);
    return $.ajax({
        url: url,
        type: 'POST',
        data: form,
        processData: false,
        contentType: false,
        async: true
    }).done(function(resp){
        if( file.name.includes("firmware") ){
            debug("success","Firmware uploading success. The module is restarting...");
            setTimeout(() => {location.reload();}, 6500);
        }else{
            debug("success",`Uploaded: ${file.name}`);
        }
    }).fail(function(resp){
        debug("error",`Failed: ${file.name}`);
    });
}

I think the problem lies in here:

for (let file of files) {
  promises.push( uploadFile(file) );
}
updatePromises();

Since the esp32 is not capable to do a task in that sort amount of time, i tried to delay the requests like this:

for (let file of files) {
  setTimeout(() => {
    promises.push( uploadFile(file) );
  }, 100); // tried also with 200,500 and 1000ms.
}
updatePromises();

I thought that the updatePromises() function is the problem because it's gets called before the promises array fills up. So i did this:

var filesIndex = 0;
for (let file of files) {
  setTimeout(() => {
    filesIndex++;
    promises.push( uploadFile(file) );
    if(filesIndex >= files.length){
       updatePromises();
    }
  }, 100); // tried also with 200,500 and 1000ms.
}

Again, no success. This is probably because the files are starting to upload before i register a callback for the promises. Interestingly the updatePromises function get's called in every case but the files are just half way there always.

If i uploading just one file with this exact same method everything works fine.

So the question is.

How can i delay the uploading ajax? Should i set up a variable to every file separatelly to indicate if it is uploaded and upload the next one?

*EDIT: Here is the working code with the accepted solution:

function fileDragN_DropEvents(){
    var $fileInput = $('.file-input');
    var $droparea = $('.file-drop-area');
    $fileInput.on('dragenter focus click', function() {
      $droparea.addClass('is-active');
    });
    $fileInput.on('dragleave blur drop', function() {
      $droparea.removeClass('is-active');
    });
    $fileInput.on('change', function() {
      var filesCount = $(this)[0].files.length;
      var $textContainer = $(this).prev();
    
      if (filesCount === 1) {
        var fileName = $(this).val().split('\\').pop();
        $textContainer.text(fileName);
      } else {
        $textContainer.text(filesCount + ' files selected');
      }
    });
}

var promises = [];
function updatePromises() {
    $.when.apply(null, promises).done(function() {
        if(promises.length > 1){
            $('.file-input').prev().text("Files uploaded");
            debug("success","Files uploaded");
        }else{
            $('.file-input').prev().text("File uploaded");
        }
        setTimeout(() => {uploadingDone();}, 500);
    })
}

function uploadingDone(){
    $("#fileUploadInput").val("");
    $("#uploadIndicatorIcon").empty();
    $('.file-input').prev().text("or drag & drop multiple files or a firmware");
}

function dragAndDrop(el){
    promises = [];
    var files = $(el).prop('files');
    var promise = $.Deferred();
    promise.resolve();

    if( !isFileArrFits(files) ){
        debug("error","Files does not fit into the filesystem");
        uploadingDone();
        return false;
    }
    $("#uploadIndicatorIcon").html( '<i class="fas fa-spinner fa-spin"></i>' );

    for (let file of files) {
        promise = promise.then( () => uploadFile(file) );
        promises.push(promise);
    }
    updatePromises();
}

function uploadFile(file){
    var url = "/uploading";
    if( file.name.includes("firmware") ){url = "/uploadingfirm"}
    form = new FormData();
    form.append("file", file, file.name);
    return $.ajax({
        url: url,
        type: 'POST',
        data: form,
        processData: false,
        contentType: false,
        async: true
    }).done(function(resp){
        if( file.name.includes("firmware") ){
            debug("success","Firmware uploaded. Module restarts..");
            setTimeout(() => {location.reload();}, 6500);
        }else{
            debug("success",`Uploaded: ${file.name}`);
        }
    }).fail(function(resp){
        debug("error",`Failed: ${file.name}`);
    });
}

here's one that uses ONLY $.Deferred

Personally I'd NEVER use $.Deffered , but, this was a challenge and I think I met it

 // below this is just placehodler var files = [1, 2, 3, 4, 5]; function uploadFile(v) { var p = $.Deferred(); console.log(`${v} starting`); setTimeout(p.resolve, Math.random() * 1000 + 500, v); return p; } function updatePromises() { $.when.apply(null, promises).done(function() { console.log(arguments); }) } var promises = []; // above this is just placeholder // this is the change var promise = $.Deferred(); promise.resolve(); for (let file of files) { promise = promise.then(() => uploadFile(file) // not required, just shows sequence of events .then(r => { console.log(`${r} finished`); return `${r} done`; }) // end of unrequired code ); promises.push(promise); } // end of changes - the line below is the same as yours updatePromises()
 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM