Перебирая файлы для FileReader, вывод всегда содержит последнее значение из цикла - PullRequest
36 голосов
/ 22 марта 2012

Я использую 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

Ответы [ 5 ]

86 голосов
/ 22 марта 2012

Проблема в том, что вы запускаете цикл сейчас , но устанавливаемые вами обратные вызовы запускаются позже (когда происходят события).К тому времени, когда они запускаются, цикл заканчивается и остается равным последнему значению.Таким образом, в вашем случае имя всегда будет показывать «file2».

Решение состоит в том, чтобы поместить имя файла в замыкание вместе с остальными.Один из способов сделать это - создать выражение для немедленного вызова функции (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]);
}
5 голосов
/ 20 января 2017

Редактировать: просто используйте let вместо var в цикле.Это устраняет проблему, которая возникла у OP (но была введена только в 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 уникален и остается в памяти.Он доступен в функции reader.onload через аргумент e.Храня дополнительные данные в объекте reader, они сохраняются в памяти и доступны через reader.onload через e.target аргумент события.

Это объясняет, почему ваш вывод:

file2 __ content1
file2__content2

, а не:

file1__content1
file2__content2

Theсодержимое отображается правильно, поскольку e.target.result является свойством самого объекта FileReader .Если бы FileReader содержал свойство имени файла по умолчанию, его можно было бы использовать, и весь этот беспорядок полностью исключался.


Слово предостережения

Это называется расширение хост-объектов (если я понимаю разницу между нативными объектами ...).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 голосов
/ 13 октября 2017

У меня была такая же проблема, решил ее с помощью Array.from

let files = e.target.files || e.dataTransfer.files;

Array.from(files).forEach(file => {
 // do whatever
})
2 голосов
/ 25 января 2019

Вместо использования 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");
    }
0 голосов
/ 06 мая 2019

Я думаю, что лучший способ решить эту проблему - это рекурсивно вызвать функцию, которая читает 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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...