Thursday, 23 January, 2020 UTC


Summary

Reading, writing and analyzing files is an essential component of software development. For security reasons, in JavaScript, we can’t directly access users’ files. If we had something like fs in Node.js, we could just steal documents from users!
First, to get a file from a user, we need to use an input element:
<input id="my-input" type="file" onChange="handleFileChange"> 
This tiny piece of code will allow our user to upload files from her machine. The handleFileChange function that we’ll create will receive some information about the uploaded files, but to be able to manipulate them we need to use the FileReader API.
Simply Uploading Your File
Here’s a simple piece of code to upload a file using an HTML form.
<form enctype="multipart/form-data" action="/upload" method="post"> <input id="file-input" type="file" /> </form> 
There is only so much you can get from an HTML form POST. If you prefer to use JavaScript to make your requests you can do something like this:
let file = document.getElementById("file-input").files[0]; let formData = new FormData(); formData.append("file", file); fetch('/upload/image', {method: "POST", body: formData}); 
↓ Here's a great JavaScript course we recommend. Plus, this affiliate banner helps support the site 🙏
File Blob Properties
In many browsers, Files have Blob properties/functions. These functions allows us to read the file. We’re going to use a file called myFile.txt which looks like this:
File content! 
(async () => { // .text() transforms the file into a stream and then into a string const fileContent = await file.text(); console.log(fileContent); // logs "File content!" // .stream() returns a ReadableStream const fileContentStream = await file.stream(); console.log(await streamToText(fileContentStream)); // logs "File content!" const buffer = await file.arrayBuffer(); console.log(bufferToText(buffer)) // logs "File content!" // .slice() allows you to get slices of the file here we take a slice of the entire file const fileSliceBlob = file.slice(0, file.length); // we convert to blob to a stream const fileSliceBlobStream = await fileSliceBlob.stream(); console.log(await streamToText(fileSliceBlobStream)); // logs "File content!" })() // We just use this function to convert streams to text const streamToText = async (blob) => { const readableStream = await blob.getReader(); const chunk = await readableStream.read(); return new TextDecoder('utf-8').decode(chunk.value); } // Not the best way to get text from a file! const bufferToText = (buffer) => { const bufferByteLength = buffer.byteLength; const bufferUint8Array = new Uint8Array(buffer, 0, bufferByteLength); return new TextDecoder().decode(bufferUint8Array); } 
The problem is that a few important browsers don’t support the File Blob properties.
Some FileReader Code
The FileReader API is used much more broadly. As you will see, we have similar features to the File interface. We also have additional features.
FileReader Lifecycle
There are 6 main events attached to FileReader:
  • loadstart: Fires when we start loading a file.
  • progress: Fires when the blob is read in memory.
  • abort: Fires when we call .abort
  • error: Fires when an error occurs
  • load: Fires when the read is successful.
  • loadend: Fires when the file is loaded and if error or abort didn’t get called or if load starts a new read.
FileReader Methods
To start loading our file we have four methods:
  • readAsArrayBuffer(file): Reads the file or blob as an array buffer. One use case is to send large files to a service worker.
  • readAsBinaryString(file): Reads the file as a binary string
  • readAsText(file, format): Reads the file as USVString (almost like a string), and you can specify an optional format.
  • readAsDataURL(file): This will return a URL where you can access the file’s content, it is Base64 encoded and ready to send to your server
Here is some code you can use to see the FileReader API in action.
<body> <input type='file' id='input'> <progress value="0" max="100" id="progress-bar"></progress> <div id="status"></div> <script> // document.getElementById('input').addEventListener('change', (e) => { const file = document.getElementById('input').files[0]; if (file) { processFile(file); } }) const processFile = (file) => { // we define fr as a new instance of FileReader const fr = new FileReader(); fr.readAsDataURL(file); // Handle progress, success, and errors // fr.onprogress = updateProgress; fr.onerror = errorHandler; fr.onabort = () => changeStatus('Start Loading'); fr.onloadstart = () => changeStatus('Start Loading'); fr.onload = ()=> {changeStatus('Loaded')}; fr.onloadend = () => loaded; // Here you can perform some operations on the data asynchronously fr.onprogress = setProgress; } // Updates the value of the progress bar const setProgress = (e) => { // The target is the file reader const fr = e.target; const loadingPercentage = 100 * e.loaded / e.total; document.getElementById('progress-bar').value = loadingPercentage; } const changeStatus = (status) => { document.getElementById('status').innerHTML = status } const loaded = (e) => { changeStatus('Load ended!'); const fr = e.target var result = fr.result; console.log('result:') console.log(result) // Here we can send the result to a server for example } const errorHandler = (e) => { changeStatus("Error: " + e.target.error.name) } </script> </body> 
You can see the code live here (open your developer console) and the source code here.
File Reader on Threads
FileReader is an asynchronous API because we do not want to block the main thread while reading files. For example, we don’t want our UI to stop working when the browser is trying to read a very large file. However, there is a synchronous version of FileReader called FileReaderSync. We can only use FileReaderSync in Web Workers. Web workers have their own thread so they won’t block the main thread. FileReaderSync uses the same methods as FileReader:
  • FileReaderSync.readAsArrayBuffer()
  • FileReaderSync.readAsBinaryString()
  • FileReaderSync.readAsText()
  • FileReaderSync.readAsDataURL()
There are no event handlers because it’s synchronous!