web-dev-qa-db-ja.com

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
36
user384241

問題は、ループを実行しているnowが、設定しているコールバックが実行されているlater(イベントが発生したとき)。それらが実行されるまでに、ループは終了し、最後の値が何であったかは変わりません。そのため、名前には常に「file2」と表示されます。

解決策は、ファイル名をクロージャー内に残りの部分を入れることです。これを行う1つの方法は、 即時呼び出し関数式(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]);
}
86
Ben Lee

編集:ループで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オブジェクトは一意であり、メモリに残ります。 e引数を介してreader.onload関数内でアクセスできます。 readerオブジェクトに追加データを保存することにより、メモリに保持され、reader.onloadイベント引数を介してe.targetからアクセスできます。

これにより、出力がなぜであるかが説明されます。

file2__ content1
file2__content2

ではなく:

file1__content1
file2__content2

e.target.resultFileReaderオブジェクト自体内のプロパティであるため、コンテンツは正しく表示されます。 FileReaderにデフォルトでファイル名プロパティが含まれていた場合、それを使用することができ、この混乱は完全に回避されました。


注意事項

これはExtended Host objectsと呼ばれます(ネイティブオブジェクトの違いを理解している場合...)。 FileReaderは、この状況で拡張されているHostオブジェクトです。多くのプロの開発者は、これを行うのは悪い習慣であり、悪であると信じています。 _NAMEが将来使用されるようになると、衝突が発生する可能性があります。この機能はどの仕様にも文書化されていないため、将来的にはbreakになる可能性があり、古いブラウザでは機能しない可能性があります。

個人的に、Hostオブジェクトに追加のプロパティを追加しても問題は発生していません。プロパティ名が十分に一意であると仮定すると、ブラウザはそれを無効にせず、将来のブラウザはこれらのオブジェクトをあまり変更しないので、問題なく動作するはずです。

これを非常によく説明している記事がいくつかあります。

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/

5
bryc

私は同じ問題を抱えていましたが、Array.fromを使用して解決しました

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

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

varを使用する代わりに、letを1つのループでのみ宣言された変数として使用します。

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

この問題を解決する最善の方法は、ブロブファイルを読み取る関数を再帰的に呼び出すことだと思います。したがって、私の場合、 スニペットコードの後に​​続く を使用して問題を解決します。これは少し複雑ですが、試したどのシナリオでも機能します。

引数として配列とインデックスを渡していないことに注意してください。それらが属するオブジェクトでそれらを呼び出す必要があります。

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