Зацикливание файлов для FileReader, вывод всегда содержит последнее значение из цикла
Я использую FileReader API для чтения файлов на локальном.
<input type="file" id="filesx" name="filesx[]" onchange="readmultifiles(this.files)" multiple="" />
<script>
function readmultifiles(files) {
var ret = "";
var ul = document.querySelector("#bag>ul");
while (ul.hasChildNodes()) {
ul.removeChild(ul.firstChild);
}
for (var i = 0; i < files.length; i++) //for multiple files
{
var f = files[i];
var name = files[i].name;
alert(name);
var reader = new FileReader();
reader.onload = function(e) {
// get file content
var text = e.target.result;
var li = document.createElement("li");
li.innerHTML = name + "____" + text;
ul.appendChild(li);
}
reader.readAsText(f,"UTF-8");
}
}
</script>
Если вход включает в себя 2 файла:
file1 ---- "content1"
file2 ---- "content2"
Я получаю этот вывод:
file2__content1
file2__content2
Как исправить код для отображения:
file1__content1
file2__content2
Ответы
Ответ 1
Проблема заключается в том, что вы запускаете цикл, но обратные вызовы, которые вы устанавливаете, запускаются позже (когда срабатывает событие). К тому моменту, когда они запускаются, цикл завершается и остается при любом последнем значении. Поэтому в вашем случае всегда будет отображаться "файл2" для вашего имени.
Решение состоит в том, чтобы оставить имя файла внутри закрытия вместе с остальным. Один из способов сделать это - создать немедленно вызванное функциональное выражение (IIFE) и передать файл в качестве параметра для этой функции:
for (var i = 0; i < files.length; i++) { //for multiple files
(function(file) {
var name = file.name;
var reader = new FileReader();
reader.onload = function(e) {
// get file content
var text = e.target.result;
var li = document.createElement("li");
li.innerHTML = name + "____" + text;
ul.appendChild(li);
}
reader.readAsText(file, "UTF-8");
})(files[i]);
}
В качестве альтернативы вы можете определить именованную функцию и вызвать ее как обычно:
function setupReader(file) {
var name = file.name;
var reader = new FileReader();
reader.onload = function(e) {
// get file content
var text = e.target.result;
var li = document.createElement("li");
li.innerHTML = name + "____" + text;
ul.appendChild(li);
}
reader.readAsText(file, "UTF-8");
}
for (var i = 0; i < files.length; i++) {
setupReader(files[i]);
}
Ответ 2
Редактировать: просто используйте let
вместо var
в цикле. Это устраняет проблему, которая возникла у ОП (но была введена только в 2015 году).
Старый ответ (интересный обходной путь):
Несмотря на то, что он не является надежным или пригодным для будущего, стоит упомянуть, что этого также можно достичь, добавив свойство в объект FileReader
:
var reader = new FileReader();
reader._NAME = files[i].name; // create _NAME property that contains filename.
Затем к нему доступ через e
в onload
функции обратного вызова:
li.innerHTML = e.target._NAME + "____" + text;
Почему это работает:
Несмотря на то, что переменная reader
заменяется несколько раз в течение цикла, как i
, new FileReader
объект new FileReader
является уникальным и остается в памяти. Он доступен в функции reader.onload
через аргумент e
. Сохраняя дополнительные данные в объекте reader
, они сохраняются в памяти и доступны через reader.onload
через e.target
события e.target
.
Это объясняет, почему ваш вывод:
file2 __content1
file2__content2
и не:
file1__content1
file2__content2
Содержимое отображается правильно, потому что e.target.result
является свойством самого объекта FileReader
. Если бы FileReader
содержал свойство filename по умолчанию, его можно было бы использовать, и весь этот беспорядок полностью исключался.
Слово предостережения
Это называется расширением хост-объектов (если я понимаю разницу между нативными объектами...). FileReader
- это хост-объект, который расширяется в этой ситуации. Многие профессиональные разработчики считают, что это плохая практика и/или зло. Столкновения могут произойти, если _NAME
когда-либо будет использоваться в будущем. Эта функциональность не задокументирована ни в одной спецификации, поэтому она может даже сломаться в будущем и может не работать в старых браузерах.
Лично я не столкнулся с какими-либо проблемами, добавив дополнительные свойства к объектам хоста. Предполагая, что имя свойства достаточно уникально, браузеры не отключают его, и будущие браузеры не слишком меняют эти объекты, оно должно работать нормально.
Вот несколько статей, которые объясняют это довольно хорошо:
http://kendsnyder.com/extending-host-objects-evil-extending-native-objects-not-evil-but-risky/
http://perfectionkills.com/whats-wrong-with-extending-the-dom/
И несколько статей о самой проблеме:
http://tobyho.com/2011/11/02/callbacks-in-loops/
Ответ 3
У меня была такая же проблема, она была решена с помощью Array.from
let files = e.target.files || e.dataTransfer.files;
Array.from(files).forEach(file => {
// do whatever
})
Ответ 4
Вместо использования var используйте let, поскольку объявленная переменная используется только в одном цикле.
for (let i = 0; i < files.length; i++) //for multiple files
{
let f = files[i];
let name = files[i].name;
alert(name);
let reader = new FileReader();
reader.onload = function(e) {
// get file content
let text = e.target.result;
let li = document.createElement("li");
li.innerHTML = name + "____" + text;
ul.appendChild(li);
}
reader.readAsText(f,"UTF-8");
}
Ответ 5
Я думаю, что лучший способ решить эту проблему - это рекурсивно вызвать функцию, которая читает BLOB файл. Так что в моем случае я решаю проблему с помощью следующего фрагмента кода, может быть, немного сложнее, но он работает в любом сценарии, который я пробовал.
Обратите внимание, что я не передавал массив и индекс в качестве аргументов. Вы должны вызывать их с объектом, к которому они принадлежат.
//Initialize blobs
var foo = new Blob(["Lorem ipsum dolor sit amet, consectetur adipiscing elit."], {
type: 'text/plain'
});
var bar = new Blob(["Sed tristique ipsum vitae consequat aliquet"], {
type: 'text/plain'
});
//Initialize array and index
var arrayOfBlobs = [foo, bar];
var arrayIndex = 0;
function fileRead () {
var me = this;
if (this.arrayIndex < this.arrayOfBlobs.length) {
var reader = new FileReader();
function bindedOnload(event) {
console.log("bindedOnload called");
console.log("reader results: ", event.target.result);
this.arrayIndex++; //Incrument the index
this.fileRead(); //Recursive call
}
//By Binding the onload event to the local scope we
//can have access to all local vars and functions
reader.onload = bindedOnload.bind(me);
reader.readAsText(this.arrayOfBlobs[arrayIndex]);
} else {
//This will executed when finishing reading all files
console.log("Finished");
}
}
//Call the fileRead for the first time
fileRead();