I am writing a program that shows previews of images by adding them to table cells. In the neighboring cells there are fields for the name, tags and source of the uploaded photos, which I also fill out, and then I upload all this information along with the files to the server. I use the FileReader object, which in turn writes files using the readAsDataUrl method. But the problem is that this method works asynchronously and as a result I see the preview not in the order in which the files will be uploaded to the server, and the previews themselves do not correspond to the files that stand behind them, it turns out a shuffle. This caused a problem, because the file descriptions that the user enters (name, tag, source) as a result do not match the picture.

Here's what it looks like (note the descriptions). Loading process:

loading

Download result:

result

Here, people have already raised and solved a similar problem, but I do not know how to apply these ideas in my case, when for each preview the elements of the table are dynamically created. Please help.

My code is:

Javascript

 $(document).ready(function () { $("#fileUpload").on('change', function () { //Get count of selected files var countFiles = $(this)[0].files.length; var imgPath = $(this)[0].value; var extn = imgPath.substring(imgPath.lastIndexOf('.') + 1).toLowerCase(); var image_holder = $("#image-holder"); image_holder.empty(); if (extn == "gif" || extn == "png" || extn == "jpg" || extn == "jpeg") { if (typeof(FileReader) != "undefined") { //loop for each file selected for uploaded. var newElem = document.createElement('table'); newElem.id = 'tl'; newElem.align = 'center'; newElem.border = 0; for (var i = 0; i < countFiles; i++) { var reader = new FileReader(); reader.onload = function (e) { var newRow = newElem.insertRow(0); var newCell1 = newRow.insertCell(0); newCell1.innerHTML = "<input type='text' class='form-control' " + "placeholder='Source' name='source' style='margin: 15px'>"; var newCell2 = newRow.insertCell(0); newCell2.innerHTML = "<input type='text' class='form-control' " + "placeholder='Tags' name='tags' style='margin: 10px'>"; var newCell3 = newRow.insertCell(0); newCell3.innerHTML = "<input type='text' class='form-control' " + "placeholder='Name' name='name' style='margin-left: 5px'>"; var newCell4 = newRow.insertCell(0); $("<img />", { "src": e.target.result, "class": "thumb-image" }).appendTo(newCell4); }; document.getElementById("image-holder").appendChild(newElem); reader.readAsDataURL($(this)[0].files[i]); image_holder.show(); } } else { alert("This browser does not support FileReader."); } } else { alert("Please select images only"); } }); }); 

HTML:

 <form method="POST" action="upload" enctype="multipart/form-data"> <input id="fileUpload" name="file" multiple="multiple" type="file"/> <br/> <input type="submit" value="Upload" class="btn btn-primary btn-lg"> <br/> <div id="image-holder"/> </form> 
  • It's too lazy to delve into the code, but since no one has yet answered you ... Your task (building up a queue of asynchronous operations) is very typical. To solve such problems, promis are usually used: learn.javascript.ru/promise . es-6 feature, out-of-the-box support is quite high: caniuse.com/#search=promise . If there is little such support, that is, the Bluebird library, there are also promises in jQuery (Deferred object), but with a strange syntax. - Duck Learns to Take Cover
  • However, I repeat, it’s now that you don’t have time to delve into what is happening in the code (maybe in the evening if there is no time and no one is clever), maybe it’s your overhead. But according to the description, the task looks exactly like this, and you need to identify the calls of the asynchronous operation (your file reader). The problem is very typical, well, very, very typical - Duck Learns to Hide
  • If you really don’t want any third-party libs at all, and es6 doesn’t fit, then send each subsequent asynchronous operation with a callback from the previous one. But I strongly advise you not to do this, but to sort it out once - spend less time - Duck Learns to Take Cover

1 answer 1

To organize a consistent download, you can use Promise.

To do this, upload an image using FileReader, you can put it into a function that will return Promise, which will be resolved when the picture is loaded.

It might look like this:

 function loadImage(image){ return new Promise(function(resolve, reject){ var fileReader = new FileReader(); fileReader.onload = function(e){ resolve(e.target.result); } fileReader.readAsDataURL(image); }); } 

Using the then function, you can subscribe to an event that occurs when Prmoise enters the ready state.

like this:

 loadImage().then(function(imageAsDataUrl){ ... }); 

Thus it is possible to assemble a chain of asynchronous operations that will be performed one after another.

The final view may be:

 var queue = Promise.resolve(); [].reduce.call(this.files,function(queue, file, index){ return queue.then(function(){ return loadImage(file).then(function(imageAsDataUrl){ var newRow = newElem.insertRow(0); var newCell1 = newRow.insertCell(0); newCell1.innerHTML = "<input type='text' class='form-control' " + "placeholder='Source' name='source' style='margin: 15px'>"; var newCell2 = newRow.insertCell(0); newCell2.innerHTML = "<input type='text' class='form-control' " + "placeholder='Tags' name='tags' style='margin: 10px'>"; var newCell3 = newRow.insertCell(0); newCell3.innerHTML = "<input type='text' class='form-control' " + "placeholder='Name' name='name' style='margin-left: 5px'>"; var newCell4 = newRow.insertCell(0); $("<img />", { "src": imageAsDataUrl, "class": "thumb-image" }).appendTo(newCell4); }); }); }, Promise.resolve()).then(function(){ // все картинки загрузились image_holder.show(); }) 

small digression: $(this)[0] is the same as this , you can check with the following expression $(this)[0] === this returns true .

  • thank you so much! The code works with a single bug - the images are now loaded in the order no longer random, but opposite to the original one. For example, before loading, I saw cats in the order of one, three, two, five, and described them the same way. But after loading they were displayed in the order of five, two, three, one, although the descriptions remained in their places. Screenshot: prntscr.com/bas36u Can I influence the issue procedure? - Oleg Shankovskyi
  • Understood, you need to apply reduceRight instead of reduce. Thank! - Oleg Shankovskyi
  • @OlegShankovskyi, the problem was not in reduce , but in how the rows are inserted, insertRow (0) inserts into the beginning, so it seems that they are loaded in the reverse order - Grundy
  • @Grundy given code provides the display of images sequentially, as the download, or the display occurs at the end of all actions? My pictures are loaded in the for loop, everything is loaded and filled normally, only the loaded one is displayed after the end of the for loop. if you debug, step by step, then everything works as it should - the picture has loaded - appeared on the screen. - Vadim
  • @ Vadim, the pictures are loaded and displayed sequentially. To say what exactly is wrong with your code, you need to see it. You can ask your own question and give an example. - Grundy