34

I'm using the FileReader API to read multiple files.

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

The problem is that all files are read at the same time, and when the files have a total size (sum) that is very large, the browser crashes.

I want to read one file after another, so that the memory consumption is reduced.

Is this possible?

1

9 Answers 9

30

I came up with a solution myself which works.

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);
}
Sign up to request clarification or add additional context in comments.

3 Comments

Why don't you create a new FileReader object for each read? I believe the FileReader object is not meant to be reused after it's state had changed from 0 (empty) to 1 (loading), and then to 2 (done). This might give you some serious browser bugs.
Nice recursive function.
If I use "reader.addListener('load', callback)". It will be double at last item.
23

I'm updating this question for the benefit of new users, who are looking for a solution to upload multiple files via the FileReader API, especially using ES.

Rather than manually iterating over each file, it's much simpler & cleaner to use Object.keys(files) in 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>

2 Comments

Array.from(files).foreach(file => {}) also works nice
For some reason this seems more javascript-ey solution to me than the accepted one.
18

This should read the files one by one:

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

1 Comment

This is good one especially when you need which file had completed onload so you can take file.type, etc.
12

Define the input using multiple property:

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

Define the upload function:

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

1 Comment

This solution should be accepted
3

My complete solution is here:

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

1 Comment

This solution is perfect!
2

I implemented another solution using modern JS (Map, Iterator). I adapted the code from my Angular application (originally written with some TS features).

Like Steve KACOU mentioned, we create a different FileReader instance for each file.

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

Basically this takes advantage of passing objects by reference to perpetuate the map with every iteration. This makes the code quite easy to read in my opinion.

Comments

1

Taking the best parts of these answers.

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

1 Comment

As far as I can tell this will not fix the issue of him not wanting files read in parallel.
-1

You must instantiate a FileReader for each file to read.

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

1 Comment

This will read the files in parallel. The OP asked for an implementation where they can be read sequentially.
-1

Try this

const setFileMultiple = (e) => {
  e.preventDefault();
  //Get the files
  let file = [...e.target.files] || [];

  file.forEach((item, index) => {
    let reader = new FileReader();

    reader.onloadend = () => {
      console.log("result", reader.result);
    };
    reader.readAsDataURL(file[index]);
  });

};

2 Comments

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
This will still read all at the same time.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.