JavaScript Object URLs

Chapter 18 17 mins

Learning outcomes:

  1. What are object URLs
  2. How to create object URLs
  3. Revoking object URLs

Introduction

Retrieving a File object from a file-selection interface is cool. We can extract out meta information about the file and use that to do numerous things - such as lay out file type checks, file size checks and so on.

But wouldn't it be even more amazing if we could somehow utilise the actual content of the file too! Imagine that we select an image using a file input and then pass its data to an <img> element, to be displayed on-the-go.

In this chapter, we shall learn about a convenient method of generating URLs that point to given File objects, ultimately serving the purpose of reading the content of those files. It is URL.createObjectURL().

In the coming chapter on the FileReader() API, though, we will dive a lot deeper in file reading, exploring the formats and techniques of the utility.

Creating URLs

The global URL object comes equipped with many methods to aid in URL processing. One of those, which we shall explore in this section, is the createObjectURL method.

URL.createObjectURL() serves to create URLs that point to given File objects (or better to say Blob objects, as we will see in the next chapter).

The method takes as argument the file object to generate a URL for.

Once provided, the file object is internally mapped to a unique URL and finally this URL is returned; which can then be passed to anything that requires a source link.

For example the returned URL can be provided to the src attribute for <img> and <script> tags, or the href attribute for <a> and <link> tags.

As can be inferred, the return value of URL.createObjectURL() is of type "string".

If URL.createObjectURL() is called without an argument, or with one that's not a File object, the method will throw a TypeError exception.

Consider the code below:

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

fileInput.onchange = function(e) {
    preview.src = URL.createObjectURL(this.files[0]);
}

Live Example

We have an input element to select a file and an img element for previewing that file. The onchange handler of the input element is configured to change the src of the image and point it to the newly-selected file (using a URL string which maps to it).

As you see here, URL.createObjectURL() is an extremely useful method to map any file object to a particular URL and then use that URL in places where URl strings are required.

In the same way, we used the method to create image file URLs here, you can use it for literally any other file!

Just make sure that whatever file you choose, to create a URL for, has some HTML element which can utilise it.

For example, creating an object URL for a Python file or a PHP file would be meaningless because no HTML element can parse these file types. In contrary, doing the same for PDF files would be fruitful as we can display PDF files in the <iframe> element.

Anyways, following is a task to check whether you can pin point a problem in the code above!

There's a problem in the code above, that will cause an exception to be thrown.

Determine this problem, and rewrite the code above to solve it.

Hint: Remember 'empty' inputs?

The problem is that of an empty input.

If the user selects no file in the file browsing interface, then the input element would become empty with no elements in the files property.

This will cause the statement URL.createObjectURL(this.files[0]) to through an exception, since this.files[0] would evaluate to undefined which is an unacceptable value for the method.

We just need to check the files property, in some way or other, and proceed only if it has an element in it. Following is one way to solve the code above:

var fileInput = document.getElementById("fileInput");
var preview = document.getElementById("preview");

fileInput.onchange = function(e) {
    var file = this.files[0];
    if (file) {
        preview.src = URL.createObjectURL(this.files[0]);
    }
}

In another variant, we can check the length property of the files object and proceed if it isn't 0.

There's still a problem in the code above, which can cause even unsupported file URLs to be added to the src attribute of the <img> element.

Determine this problem, and rewrite the code above to solve it.

Hint: Supported file objects contain the substring "image" in their type property!

The problem is that when we select a file in the file input element, it might not be an image file - such as an HTML file, a CSS file or any other file.

In such a case, we can't assign the file's URL to the src attribute of the <img> element, since it can't parse non-image files!

What we need to do instead, is to first check the type of the selected file and proceed only if it contains the "image" substring in it. This is because all image files have a MIME type which begins with image/.

Following is the corrected code:

var fileInput = document.getElementById("fileInput");
var preview = document.getElementById("preview");

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

Depending on your case, you can give an else block here as well to deal with the unsupported file.

Revoking objects

So uptil this point we know that an object URL can be created by calling the URL.createObjectURL() method, and it can be passed to anything which requires source links.

However, note that an object URL is only valid upto the point its corresponding File data is in memory.

That is, unless the file is revoked (cleared away from memory), either explicitly or implicitly, the URL can continue pointing to the file without any problem.

Otherwise the URL will fail!

Implicit revoking, also known as automatic revoking, is performed automatically by the browser the moment we close the browser tab. The corresponding File's data is cleaned up from the memory and likewise subsequent calls to the URL fail.

On the other hand, explicit revoking, also known as manual revoking, is accomplished using the URL.revokeObjectURL() method.

In most cases, manual revoking isn't usually necessary. However, in some cases it's definitely worth the effort as it helps optimise resources by garbage-collecting unwanted file objects from memory.

URL.revokeObjectURL() takes an argument which is the object URL whose associated file we wish to revoke.

In the example that follows, we use this method to free up memory, each time a new file is selected in the input element, by garbage-collecting the previous file.

The example below is an extension to the first example on this page.
var fileInput = document.getElementById("fileInput");
var preview = document.getElementById("preview");
var prevURL, newURL;

fileInput.onchange = function(e) {
    // first clear the previous URL
    URL.revokeObjectURL(prevURL);

    // then create a new one
    newURL = URL.createObjectURL(this.files[0]);
    preview.src = newURL;

    // now the new URL becomes the previous URL
    prevURL = newURL;
}

Live Example

The way you can test the result of adding URL.revokeObjectURL() here is described as follows:

Select an image file, then once it's displayed, right-click on it and choose Open in new tab. This will open the image object in a new tab. Let this tab remain opened, and then go to the file input interface and select another file.

Once this is done, go back to the previously opened tab and refresh it - you'll encounter an error saying something like 'No file found'. This happens because with the selection of a new file, the previous file object is cleared away from memory.

In one of their examples, MDN utilises URL.revokeObjectURL() very effectively - in the onload handler for image elements, to clean up the image file as soon as it's loaded!

Following we demonstrate a simpler version of their example:

var fileInput = document.getElementById("fileInput");
var preview = document.getElementById("preview");
var newURL;

preview.onload = function(e) {
    // once loaded, remove the image object from memory
    URL.revokeObjectURL(prevURL);
}

fileInput.onchange = function(e) {
    // create an object URL
    newURL = URL.createObjectURL(this.files[0]);
    preview.src = newURL;
}

The first two lines are the same as in the code above, so there's nothing new to share in there! In the third line, however, we remove the declaration for prevURL just because it isn't needed here.

Moving on, we assign an onload handler to the preview image element whose use we'll discuss shortly below.

After this, in fileInput's onchange handler, we create an object URL out of the selected file and then assign it to the src attribute of preview.

The moment this image loads, and therefore displays completely, the onload handler, we created before, takes over. It merely removes the image's data from memory by calling URL.revokeObjectURL(newURL).

Since the image is painted by the browser, by the time its load event occurs, it isn't at all necessary to keep it in memory!

Obviously, as with the code snippets in the previous section, this code snippet also has some errors in it which need to be rectified before implementing it on deployment level. To spot the errors, do Task 1 and Task 2.

What's the problem in the code below?

var fileInput = document.getElementById("fileInput");
var preview = document.getElementById("preview");
var prevURL, newURL;

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

        newURL = URL.createObjectURL(file);
        preview.src = newURL;

        prevURL = newURL;
    }
}
  • A File object is passed to URL.revokeObjectURL()
  • There's no problem