循环遍历FileReader的文件,输出始终包含循环的最后一个值

时间:2012-03-22 02:28:11

标签: javascript html5 javascript-events 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

6 个答案:

答案 0 :(得分:84)

问题是你现在正在运行循环 ,但你正在设置的回调正在以后运行(当事件触发时)。当它们运行时,循环结束并保持在最后一个值。因此,在您的情况下,它将始终显示“file2”作为名称。

解决方案是将文件名放在一个闭包中,其余部分。一种方法是创建一个immediately-invoked function expression (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]);
}

答案 1 :(得分:5)

修改:只需在循环中使用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变量在循环期间被替换多次,如inew FileReader对象也是唯一的并保留在内存中。它可以通过reader.onload参数在e函数中访问。通过在reader对象中存储其他数据,它将保存在内存中,并可通过reader.onload事件参数e.target访问。

这解释了为什么你的输出是:

  

<强> file2的 __内容1
file2__content2

而不是:

  

file1__content1
file2__content2

内容显示正确,因为 e.target.resultFileReader对象本身中的属性。如果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/

答案 2 :(得分:3)

我遇到了同样的问题,使用Array.from

解决了这个问题
let files = e.target.files || e.dataTransfer.files;

Array.from(files).forEach(file => {
 // do whatever
})

答案 3 :(得分:1)

使用 let 代替使用 var ,因为声明的变量仅在一个循环中使用。

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");
    }

答案 4 :(得分:0)

我认为解决此问题的最佳方法是递归调用一个读取blob文件的函数。因此,以我为例,我使用following snippet code解决了这个问题,也许有点复杂,但是可以在我尝试过的任何情况下使用。

请注意,我没有将数组和索引作为参数传递。您需要使用它们所属的对象来调用它们。

//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();

答案 5 :(得分:0)

您可以对循环中的文件进行承诺/回调。

承诺-

  fileBase64(file) {
    return new Promise((resolve, reject) => {
      let reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = function() {
        resolve(reader.result);
      };
      reader.onerror = function(error) {
        reject(error);
      };
    });
  }

我在onClick上调用此函数

  onClick = async () => {
    for (var i = 0; i < this.state.company_bank_statement_array.length; i++) {
      let file = document.getElementById(
        this.state.company_bank_statement_array[i]
      );
      let fileData = await this.fileBase64(file.files[0]);
      this.state.bankStatements.push({
        data: fileData,
        filename: file.files[0].name,
      });
    }
  };