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:
readAsText()
: the simplest of all, this method reads a file object as plain text. The content of the file goes into theresult
property as a string value.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 insidesrc
for<img>
elements.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.readAsBinaryString()
: although gone stale now, this method is only provided as a legacy of the old days ofFileReader
. 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:
load
occurs when the file-reading operation completes.error
occurs when the file couldn't be read for some reason.abort
occurs when the file-reading operation is canceled by calling theabort()
method.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()
.
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:
result
- it holds the result of the reading operation if one has been called, or the valuenull
error
- holds anError
object detailing any error that occured in the reading operation, or else the valuenull
.readyState
- holds the state of theFileReader
object, as a number.0
('empty') indicates that no reading operation has been performed on theFileReader
object;1
('loading') indicates that it is still in the process of reading a file; and2
('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"});
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.
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:
body {
background-color: yellow;
}
We'll be retrieving this file using an input
element and ultimately reading it via the readAsText()
method.
"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]);
}
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;
}
onload
handler on fr
- the rest of the code is exactly the same.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!
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]);
}
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">
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;
}
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.
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);
}
}