Can we make an infinitely downloading file using only JavaScript?
Oct 30, 2015 · 5 min readBe careful who you share it with.
Question is in the title. I don't know if the answer feels logical to most, but it wasn't to me when I first thought about it. Because:
- You can download a finite file generated by a server.
- You can try to download an infinite file generated by a server.
- You can download a finite file generated by JavaScript.
Why couldn't you also try to download an infinite file generated by only JavaScript? (I mean, without using any fancy web API like websockets or webRTC)
So if you're here for a short answer to the title: You can't.
I initially got the idea to try to answer this question when I first saw OVH's promotion on a few domain names. ".download" was only 0.50€ for a year, and thought it was worth the time to try to make a joke page with an infinitely downloading file (actually streaming content, not just a frozen download).
So I bought "cantstopthe.download". The goal was to set up a very simple HTML page, with a link to start the download of that file. But I don't want to generate useless network traffic ; the download should be entirely happening on the client's computer.
Infinite download from a server
Let's start by remembering how easy it is to make this to work with a server. The trick of course is not to store an infinitely large file and serve it, but to generate it as the user is downloading.
This isn't even remotely a challenge : Just provide a link to download a file that executes an infinite loop when queried. Here's a trivial example of this in PHP :
index.html
<a href="infinite.php" download>Link</a>
index.php
<?php while(1) echo "Hi."; ?>
If you try to get the infinite.php
file, you'll receive endless "Hi." until you manually stop the download.
Now, things get more interesting when you want to delete the server part.
Of course, the interest of this question comes from the fact that we are able to generate the download of a finite file entirely in JavaScript.
I see two main ways to achieve the download of a file from an user's own browser : Either build a File from a Blob and link to it, or use a Data URI to represent the file.
Take 1 : Link to a built file
We can use the Blob
Web API to create a representation of a file.
So first, we'll create a finite text File to download using a Blob
, and we'll provide a link pointing to its created
content.
index.html
<script src="script.js"></script>
<a id="link" download="file.txt">Download !</a>
script.js
window.onload = function() {
var a = document.getElementById('link');
a.href = window.URL.createObjectURL(
new Blob(['Hi ! This is text.'],
{type: 'text/plain'}
)
);
}
Clicking the link effectively downloads the text file. So, we just generated a file using only JavaScript, directly in the client's browser. We're not that far from what we can do with a server as we saw it, right ? Well...
What represents the file here is the blob, which is not executable code. So for our file to be downloading infinitely,
we would need an infinite Blob
. We can of course create a very long string with a loop that we would then assign to
the Blob
. But we would be trapped in that loop that creates the string, not in the download of it. So it seems like
we're executing code a bit too far from our target to be able to make the file infinitely download.
Take 2 : Data URI
Using a Data URI is not that much different from the final result we
obtained using the blob. We want to generate an infinitely large Data URI that would represent our file. But this time,
we don't need to assign it to an other variable (as we did with the Blob
). So we can try to build the file directly
with the string, without having to keep it in memory.
We could try to generate the infinitely large URI in a loop, but we would be stuck in that loop the same way we talked about before.
For a finite file, we don't even need JavaScript at all, this time plain HTML is sufficient to make the download :
index.html
<a href="data:text;charset=utf-8;base64,VGhpcyBpcyB0ZXh0IHRvbyAh"
download="file.txt">Download !</a>
The next step is to build the URI at the time the user tries to access it. We'll need to evaluate the value as late as possible.
Lazy evaluation ?
An idea would be to evaluate the string lazily, at the moment it's asked for. We'll need to change the code a bit. We
don't want to assign a value to the href
property, because we'd have to build the infinite string that before the
user clicks the link, which we saw is impossible. Instead, we'd like to build the data as the user requests it.
Here is an other example of a script that downloads a file.
index.html
<script src="script.js"></script>
<a href="javascript:download();">Download !</a>
script.js
function download() {
document.location =
'data:Application/octet-stream,'
+ 'infinite_string_should_be_here';
}
In this example, I'm using data:Application/octet-stream
so that browsers won't know how to interpret the incoming
data, and will try to download the incoming file as a result.See the infinite_string_should_be_here
string ? We can now try to replace this with a lazy string, right ?
The library Stream.js gives us a way to manipulate objects that behave like streams and generators. Which is what we want, right ? We'll just have to make a stream that generates an infinite amount of characters.
Here's what our code could be resembling...
index.html
<script src="stream-min.js"></script>
<script src="script.js"></script>
<a href="javascript:download();">Download !</a>
script.js
function download() {
// generator always generates "Hi."
var generator = Stream.generate(function() {
return "Hi.";
});
// takeWhile(n) retrieves elements while n holds true.
// join() returns a concatenation of the stream.
document.location =
'data:Application/octet-stream,'
+ generator.takeWhile(true).join();
}
The tab freezes : All of this for an infinite loop, and not the one we wanted. Maybe you saw it coming when true
is
sitting just next to while
, though...
The explanation is trivial : Even with tons of laziness, the right-side of the expression has to be fully evaluated
before being assigned. The end will of course never happen here, leaving the interpreter collect as many Hi.
as it
can handle until the tab crashes. And so ends my attempts.
Conclusion
What would it take to actually be able to achieve this ? The problem here is that whatever we do, we'll need to evaluate the full content to be downloaded before being able to start. I can't see any way we could detach the process generating the data from the process trying to get it for now.
I tried to answer this question at first because I fount it amusing and I didn't really got any satisfying answer after googling a bit. After having found my answer, I'd say this was a fun small research for me. :)
Category: Development