Чтение нескольких файлов с помощью Javascript FileReader API по одному

Я использую API FileReader для чтения нескольких файлов.

<html> <body>
    <input type="file" id="filesx" name="filesx[]"
      onchange="readmultifiles(this.files)" multiple=""/>
    <div id="bag"><ul/></div>
<script>
window.onload = function() {
    if (typeof window.FileReader !== 'function') {
        alert("The file API isn't supported on this browser yet.");
    }
}

function readmultifiles(files) {
    var ul = document.querySelector("#bag>ul");
    while (ul.hasChildNodes()) {
        ul.removeChild(ul.firstChild);
    }

    function setup_reader(file) {
        var name = file.name;
        var reader = new FileReader();
        reader.onload = function(e) {
            var bin = e.target.result; //get file content

            // do sth with text

            var li = document.createElement("li");
            li.innerHTML = name;
            ul.appendChild(li);
        }
        reader.readAsBinaryString(file);
    }

    for (var i = 0; i < files.length; i++) { setup_reader(files[i]); }
}
</script> </body> </html>

Проблема в том, что все файлы читаются одновременно, и когда общий размер (сумма) файлов очень большой, браузер вылетает.

Я хочу читать один файл за другим, чтобы уменьшить потребление памяти.

Это возможно?


person xaedes    schedule 20.12.2012    source источник
comment
Только что реализованное решение для этого, рекомендую это решение - stackoverflow.com/a/13975129/984471   -  person Manohar Reddy Poreddy    schedule 19.10.2019


Ответы (8)


Я сам придумал решение, которое работает.

function readmultifiles(files) {
  var reader = new FileReader();  
  function readFile(index) {
    if( index >= files.length ) return;
    var file = files[index];
    reader.onload = function(e) {  
      // get file content  
      var bin = e.target.result;
      // do sth with bin
      readFile(index+1)
    }
    reader.readAsBinaryString(file);
  }
  readFile(0);
}
person xaedes    schedule 20.12.2012
comment
Почему бы вам не создать новый объект FileReader для каждого чтения? Я считаю, что объект FileReader не предназначен для повторного использования после того, как его состояние изменилось с 0 (пусто) на 1 (загрузка), а затем на 2 (готово). Это может привести к серьезным ошибкам браузера. - person Pacerier; 27.07.2015
comment
Хорошая рекурсивная функция. - person jj_; 22.02.2020
comment
Если я использую reader.addListener('load', callback). Он будет двойным в последнем элементе. - person Hieu - 7347514; 26.01.2021

Это должно читать файлы один за другим:

function readmultifiles(files) {
    var ul = document.querySelector("#bag>ul");
    while (ul.hasChildNodes()) {
        ul.removeChild(ul.firstChild);
    }
    // Read first file
    setup_reader(files, 0);
}

// Don't define functions in functions in functions, when possible.

function setup_reader(files, i) {
    var file = files[i];
    var name = file.name;
    var reader = new FileReader();
    reader.onload = function(e){
                        readerLoaded(e, files, i, name);
                    };
    reader.readAsBinaryString(file);
    // After reading, read the next file.
}

function readerLoaded(e, files, i, name) {
    // get file content  
    var bin = e.target.result;
    // do sth with text

    var li = document.createElement("li");
    li.innerHTML = name;
    ul.appendChild(li);

    // If there's a file left to load
    if (i < files.length - 1) {
        // Load the next file
        setup_reader(files, i+1);
    }
}
person Cerbrus    schedule 20.12.2012
comment
Это хорошо, особенно когда вам нужно, какой файл был загружен, чтобы вы могли взять file.type и т. д. - person Manohar Reddy Poreddy; 19.10.2019

Я обновляю этот вопрос для новых пользователей, которые ищут решение для загрузки нескольких файлов через API FileReader, особенно при использовании ES.

Вместо того, чтобы вручную перебирать каждый файл, гораздо проще и чище использовать Object.keys(files) в ES:

<input type="file" onChange="readmultifiles" multiple/>
<script>
function readmultifiles(e) {
  const files = e.currentTarget.files;
  Object.keys(files).forEach(i => {
    const file = files[i];
    const reader = new FileReader();
    reader.onload = (e) => {
      //server call for uploading or reading the files one-by-one
      //by using 'reader.result' or 'file'
    }
    reader.readAsBinaryString(file);
  })
};
</script>
person Adil    schedule 17.08.2017
comment
Array.from(files).foreach(file => {}) также хорошо работает - person Ricardo Saracino; 04.04.2019

Мое полное решение здесь:

 <html> <body>
    <input type="file" id="filesx" name="filesx[]"
      onchange="readmultifiles(this.files)" multiple=""/>
    <div id="bag"></div>
<script>
window.onload = function() {
    if (typeof window.FileReader !== 'function') {
        alert("The file API isn't supported on this browser yet.");
    }
}

function readmultifiles(files) {
  var reader = new FileReader();  
  function readFile(index) {
    if( index >= files.length ) return;
    var file = files[index];
    reader.onload = function(e) {  
      // get file content  
      var bin = e.target.result;
      // do sth with bin
      readFile(index+1)
    }
    reader.readAsBinaryString(file);
  }
  readFile(0);

 function setup_reader(file) {
        var name = file.name;
        var reader = new FileReader();

            var ul = document.createElement("ul");
            document.getElementById('bag').appendChild(ul);
        reader.onload = function(e) {
            var bin = e.target.result; //get file content

            // do sth with text

            var li = document.createElement("li");
            li.innerHTML = name;
            ul.appendChild(li);
        }
        reader.readAsBinaryString(file);
    }

    for (var i = 0; i < files.length; i++) { setup_reader(files[i]); }
}
</script> </body> </html>
person Nagnath Mungade    schedule 13.08.2019
comment
Это решение идеально! - person khalid MZIBRA; 29.04.2020

Я реализовал другое решение, используя современный JS (Map, Iterator). Я адаптировал код из своего приложения Angular (изначально написанного с некоторыми функциями TS).

Как упоминал Стив КАКОУ, мы создаем отдельный экземпляр FileReader для каждого файла.

<input type="file" id="filesx" name="filesx[]"
      onchange="processFileChange(this)" multiple=""/>
    function processFileChange(event) {
        if (event.target.files && event.target.files.length) {
            const fileMap = new Map();

            for (let i = 0; i < event.target.files.length; i++) {
                const file = event.target.files[i];
                const fileReader = new FileReader();
                fileMap.set(fileReader, file);
            }

            const mapEntries = fileMap.entries();
            readFile(mapEntries);
        }
    }

    function readFile(mapEntries) {
        const nextValue = mapEntries.next();

        if (nextValue.done === true) {
            return;
        }

        const [fileReader, file] = nextValue.value;

        fileReader.readAsDataURL(file);
        fileReader.onload = () => {
            // Do black magic for each file here (using fileReader.result)

            // Read the next file
            readFile(mapEntries);
        };
    }

По сути, это использует передачу объектов по ссылке, чтобы увековечить карту с каждой итерацией. Это делает код довольно легко читаемым, на мой взгляд.

person Tudor Merlas    schedule 20.10.2020

Вы должны создать экземпляр FileReader для каждого файла для чтения.

function readFiles(event) {
  //Get the files
  var files = event.input.files || [];
  if (files.length) {
    for (let index = 0; index < files.length; index++) {
      //instantiate a FileReader for the current file to read
      var reader = new FileReader();
      reader.onload = function() {
        var result = reader.result;
        console.log(result); //File data
      };
      reader.readAsDataURL(files[index]);
    }
  }
}

person De Bonheur    schedule 19.01.2020

Определите ввод, используя свойство multiple:

<input onchange = 'upload(event)' type = 'file' multiple/>

Определите функцию загрузки:

const upload = async (event) => {
  
    // Convert the FileList into an array and iterate
    let files = Array.from(event.target.files).map(file => {

        // Define a new file reader
        let reader = new FileReader();

        // Create a new promise
        return new Promise(resolve => {

            // Resolve the promise after reading file
            reader.onload = () => resolve(reader.result);

            // Read the file as a text
            reader.readAsText(file);

        });

    });

    // At this point you'll have an array of results
    let res = await Promise.all(files);
  
}
person Erik Martín Jordán    schedule 28.04.2021

Взяв лучшие части этих ответов.

<input type="file" onchange="readmultifiles(this.files)" multiple />
<script>
function readmultifiles(files) {
  for (file of files) {
    const reader = new FileReader();
    reader.readAsBinaryString(file);
    reader.fileName = file.name;
    reader.onload = (event) => {
      const fileName = event.target.fileName;
      const content = event.currentTarget.result;
      console.log({ fileName, content });
    };
  }
}

</script>
person Leon    schedule 28.06.2020
comment
Насколько я могу судить, это не решит проблему того, что он не хочет, чтобы файлы читались параллельно. - person Tudor Merlas; 20.10.2020