The FileReader API

Chapter 20 27 mins

Learning outcomes:

  1. What is FileReader
  2. Working with FileReader
  3. The readAsText() method
  4. The readAsDataURL() method

Introduction

So far in this unit devoted to exploring file-handling utilities in JavaScript, we've covered some solid ground on a couple of file-related APIs in the language.

In particular we've seen the File and Blob interfaces in detail and even a method to use them i.e URL.createObjectURL(). If your feel you need to revise these concepts before beginning you can simply read them at File API, Blobs, and Object URLs, respectively.

In this last chapter, we shall cover perhaps the most detailed of those APIs, that, is the FileReader API. It is used solely to read files and extract out their content for further usage for example, passing on the data to an AJAX request and so on.

Given that there's so much to cover in this chapter, let's not waste anymore time in discussing just abstract details, and rather get into the real business!

What is FileReader?

JavaScript provides developers an easy way to read selected File or Blob objects and that is using the FileReader interface.

But what exactly is FileReader?

FileReader() is an asynchronous API which has utilities to aid in reading files.

It's based on an event model which where we can perform procedures on the occurence of event such as load, erro, abort and so on.

Now one might ask that in what cases would we even need to use FileReader(), given that we have the URL.createObjectURL() method to create URLs for given resources, and hence indirectly read them.

Now one might ask that how is a FileReader different from the URL.createObjectURL() method? Don't they both read files?

Technically no!

As we know from the Object URLs chapter, URL.createObjectURL() takes a Blob or File object and merely maps it to a URL, which can then be passed to anything that requires a source link, such as the src attribute on <img> elements.

It can't suffice, if we want to retrieve the content of the file, and let's say save it an identifer - it just isn't meant to do that!

In contrast, FileReader is solely meant to read a given Blob or File object and then save its content in its own result property, which can then be, obviously, assigned to any identifer and used likewise.

For instance, using FileReader we can build applications that upload files selected by the user, in an input element, to the server entirely using AJAX.

To summarise it:

URL.createObjectURL() doesn't read a file, but rather only creates a URL pointing to it. When we visit the URL on the browser, it treats the URL just like it treats any other URL.

FileReader, on the otherhand, does indeed read a file and make its data actually accessible to us via its result property. It's the API we'll use if we want to process file data as in uploading files using AJAX.

Moving on, another question developers get confused around is that: why is FileReader asynchronous in nature?

Now this is a good question!

Why is FileReader asynchronous?

See the idea in JavaScript is that anything that potentially takes time, for example the setTimeout() function, AJAX requests etc, and therefore has the tendency to block the rendering of the HTML page, is made asynchronous by the language.

Asynchronous operations work as follows:

A statement calls for the execution of an asynchronous operation - the JavaScript engine sets it up in the background and continues on the next operation. The moment this operation completes, it is queued up in an internal task queue maintained by the JavaScript engine.

Only once the call stack (where all instructions get executed) empties is when we give attention to the task queue and likewise execute all the awaiting operations there.

Coming back to the topic, FileReader is no exception to this logic.

Reading files can be a time consuming process especially in cases where the files are enormous in size. Likewise, if FileReader was to be a synchronous API, it would've blocked rendering until the completion of a file reading procedure, which would be gross!

Thereby, FileReader works in an asynchronous manner, driven by events.

We read a file using FileReader, some events are dispatched by it, and finally we handle these events using handler functions. Simple as it can get!

Now with a good foundation set on the API, let's now see how to actually work with it.

Working with FileReader

We start by instantiating an object out of the FileReader() constructor, as we do with many APIs in JavaScript.

var fr = new FileReader();

This spawns an object which, as we'll see below, holds literally a great deal of features to read files.

After this, we only need to call one of the four file-reading methods defined by the FileReader interface and handle given events to react to the file-reading operation.

First let's cover the file-reading methods.

All, essentially, require an argument which is the Blob object to read and put the result of the reading operation inside the result property of the FileReader object once it reaches completion.

We have:

  1. readAsText(): the simplest of all, this method reads a file object as plain text. The content of the file goes into the result property as a string value.
  2. readAsDataURL(): this method reads the given file object and then converts it into a data URL representation. The benefit of doing so is that the file object can act as the source of any HTML element, for example it can go inside src for <img> elements.
  3. readAsArrayBuffer(): reads the content of the given file object into an array buffer. This buffer can then be processed further, such as manipulating the individual bytes, or used in a couple of more handful APIs.
  4. readAsBinaryString(): although gone stale now, this method is only provided as a legacy of the old days of FileReader. It reads the file object into a string of bits, referred to as a binary string.

For now, we will use readAsText() to give a simple example of reading a file using FileReader.

But before that let's see some other aspects of the FileReader API - the methods above sure read the provided file object in a given format, but we need to consider other things too, in order to deal with the result.

This involves handling a couple of events and understanding a couple of properties.

As with XMLHttpRequest(), FileReader dispatches the events load, abort, error, and progress as well as the more recent variants of load.

To briefly discuss them:

  1. load occurs when the file-reading operation completes.
  2. error occurs when the file couldn't be read for some reason.
  3. abort occurs when the file-reading operation is canceled by calling the abort() method.
  4. progress occurs at regular intervals while the file object is being read.

As we can guess, handling these events follows the same, old JavaScript conventions - we can use the corresponding on- handlers, for instance onload for load, onprogress for progress and so on.

Not only this but, since FileReader objects inherit from EventTarget, the aforementioned events can also be listened to using addEventListener().

Read more about inheritance at JavaScript Object Prototypes.

Moving over to the properties of a FileReader object we've got the following.

Excluding the event handler properties as discussed above, a FileReader object essentially has three useful properties to placehold some useful ideas about the object.

They are:

  1. result - it holds the result of the reading operation if one has been called, or the value null
  2. error - holds an Error object detailing any error that occured in the reading operation, or else the value null.
  3. readyState - holds the state of the FileReader object, as a number. 0 ('empty') indicates that no reading operation has been performed on the FileReader object; 1 ('loading') indicates that it is still in the process of reading a file; and 2 ('done') indicates that a file reading operation has been completed.
readyState is used internally by the JavaScript engine when working with a FileReader object.

With these properties and events in place, let's now finally conclude our simple example.

For simplicity, we create the following text blob which will ultimately be read using a file reader:

var blob = new Blob(["Hello World"], {type: "text/plain"});
For more info on Blob please consider reading JavaScript Blobs.

For the real deal, consider the code below:

var blob = new Blob(["Hello World"], {type: "text/plain"});

var fr = new FileReader();

fr.onload = function() {
    console.log(this.result);
}

fr.readAsText(blob);

What's happening here is fairly clear, nonetheless explained below:

First we create a FileReader object, then give it an onload handler which will fire once a reading operation completes, and finally read blob using the method readAsText().

Once this blob is read completely, the onload handler fires and consequently we get the result of the reading operation logged.

In this case, the result is a plain text value.

Hello World

Reading as text

As we've seen just right now, readAsText() is perhaps the most basic of all FileReader's reading methods.

To recap, it reads a file into a plain textual representation and is likewise preferable for all sorts of text files.

In particular all files that start with the word text in their MIME types or have underlying source codes such as HTML, CSS, JavaScript, SVG, XML files - are collectively referred to here as text files.

This obviously includes the all-time favourite, plain .txt files.

Consider the style.css file below:

style.css
body {
    background-color: yellow;
}

We'll be retrieving this file using an input element and ultimately reading it via the readAsText() method.

For this example, you'll need create a new CSS file, name it "style.css" and put the above code in it so that you can visualise everything right on your machine too. If you're too lazy to do so simply download the style.css file.

Following is the code to get the reading logic in place:

<input type="file" id="fileInput">
var fileInput = document.getElementById("fileInput");

var fr = new FileReader();

fr.onload = function() {
    alert(this.result);
}

fileInput.onchange = function(e) {
    fr.readAsText(this.files[0]);
}

Live Example

If you notice for a while, nothing is new in here - we have an input element to select files and its onchange handler to respond to file selections.

Once a file is selected (which we assume is the style.css file) this handler fires and consequently calls the fr.readAsText() method on the selected file object.

And once the file is read completely, the onload handler set on fr in line 3, gets executed - ultimately alerting the content of the file.

In the example below we go a bit far than just alerting the content of style.css - this time put it inside a <style> tag:

<style id="s"></style>
var s = document.getElementById("s");
fr.onload = function() {
    s.innerHTML = this.result;
}
Here we've only shown the onload handler on fr - the rest of the code is exactly the same.

Live Example

One thing to remember in this example is that if you select a file with non-CSS content, the <style> tag won't be able to parse it!

So it's best to put a rough check for this i.e get the type property of the selected file and match it against "text/css" - simple as that!

Create a file input element with an onchange handler that checks whether the selected file is a textual file or not, and consequently logs its content if it's one.

To check for a textual file, use the type property of the selected File object and compare it against some value.

<input type="file" id="fileInput">
var fileInput = document.getElementById("fileInput");

var fr = new FileReader();

fr.onload = function() {
    console.log(this.result);
}

fileInput.onchange = function(e) {
    var file = this.files[0];
    if (file && file.type.indexOf("text") !== -1) {
        fr.readAsText(file);
    }
}

Reading as data URL

Reading a file as a text value is everything - it's good, simple, basic, interesting - but, unfortunately not the case everytime!

Sometimes we also need to deal with binary content. Who can ignore the importance of image files in applications, especially the profile picture uploads!

Reading binary data using readAsText() isn't useful at all - it tends to produce gibberish and likewise should be avoided in all circumstances!

But then what should we use to deal with binary data? Well, readAsDataURL() is a good choice!

The readAsDataURL() method reads a file and converts its content into a base-64, data URL representation.

The benefit of doing so is that the result of the reading operation can be directly passed to anything that requires a source link such as the src attribute on <img> elements.

This is because the entire content of the file is put inside a string; which, as we know, is the only acceptable data type for src and href attributes; and because the string represents a data URL which browsers effectively treat as a file.

If you didn't understand this here's what it means:

The src attribute on an <img> element requires a string value and indeed readAsDataURL() returns one.

Moreover, the value must be a link to some file data and once again, the string returned by readAsDataURL() indeed returns a widely accepted file format for representing files i.e a data URL.

To boil it down, it's a win-win situation!

Let's consider an elementary example.

We'll read the same style.css file above, however this time, as a data URL and then alert the result:

<input type="file" id="fileInput">
var fileInput = document.getElementById("fileInput");

var fr = new FileReader();

fr.onload = function() {
    alert(this.result);
}

fileInput.onchange = function(e) {
    fr.readAsDataURL(this.files[0]);
}

Live Example

The moment the style.css file is read completely by fr, the following string gets saved into its result property:

data:text/css;base64,Ym9keSB7DQogICAgYmFja2dyb3VuZC1jb2xvcjogeWVsbG93Ow0KfQ==

We have a CSS file represented as a base-64 encoded data URL. Amazing!

Now just as before, we'll use this method and the CSS file style.css to actually apply its underlying rules to the HTML document. However, this time, there will be some changes!

Since we are dealing with a URL, and not with the actual content of style.css, we'll create an HTML tag that accepts links to given stylesheets. Can you guess what's about to come?

Yes, that's right - the <link> tag!

We'll create a link tag and then pass to it the result of reading style.css using readAsDataURL(). Let's dive into the code:

<link rel="stylesheet">
The rel="stylesheet" attribute is very important! If you omit it, the browser won't be able to understand that the tag represents a stylesheet and so won't apply the given styles!
var link = document.getElementsByTagName("link")[0];

fr.onload = function() {
    link.href = this.result;
}

Live Example

Akin to the example above, once again here we first select the <link> tag inside the variable link, and then inside fr's onload, set its href to the generated data URL.

What you ought to remember while working with data URLs is that, as the name suggests, they behave just like any other URL - we can enter them in the browser's address bar or simply assign them to HTML element attributes and so on.

However, unlike all other URLs, they don't point to any location - rather they self-contain the data they represent!

Create a file input element with an onchange handler that checks whether the selected file is an image file or not, and consequently shows its preview in an <img> element if it's one.

You may use the following markup for the <img> element and select it, in JavaScript, using its id:

<img id="preview">

To check for an image file, use the type property of the selected File object and compare it against some value.

<input type="file" id="fileInput">
<img id="preview">
var fileInput = document.getElementById("fileInput");
var preview = document.getElementById("preview");

var fr = new FileReader();

fr.onload = function() {
    preview.src = this.result;
}

fileInput.onchange = function(e) {
    var file = this.files[0];
    if (file && file.type.indexOf("image") !== -1) {
        fr.readAsDataURL(file);
    }
}

Live Example