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>