11

I'm saving a file in JavaScript using the following code:

var a = document.createElement('a');
a.href = URL.createObjectURL(new Blob(['SOME DATA']));
a.download = 'some.dat';
a.click();

I want to revoke the URL (using URL.revokeObjectURL) once the file is downloaded. When is it safe to do so?

Can I revoke it immediately after calling a.click() (which seems to work, but I'm not sure it's safe)? In a's click event listener? Is there a way to make a click event listener run after the default action?

Steve Trout
  • 9,261
  • 2
  • 19
  • 30

2 Answers2

5

a.click() on a DOM element simulates a click on the element, instead of propagation of the click event, so it's directly sent to the browser. I believe it would be a little bit safer to move revoking of URL object to another event cycle using a timer:

setTimeout(function() {
 URL.revokeObjectURL(a.href);
}, 0);
Don McCurdy
  • 10,975
  • 2
  • 37
  • 75
Stanislav Šolc
  • 306
  • 3
  • 8
  • You mean `setTimeout`? – Sumurai8 May 15 '16 at 16:45
  • click() does fire events though -- see [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click). That implies to me that it's asynchronous, so will run after the current JS execution cycle. – Steve Trout May 15 '16 at 16:55
  • 1
    Hello Steve, you are right. I quess click() internaly adds to event loop click event (according to MDN only bubbling phase) as normal user's click does. But by seting timer, i quess you postpone revoking of URL object even efter processing of these events. But you can always set higher tiemout :) – Stanislav Šolc May 15 '16 at 17:18
  • 4
    With this you probably delete the file before its downloaded. A very bad advise and definitely not the solution to this question. – ChrisG Jun 13 '20 at 19:44
  • See https://stackoverflow.com/a/66905746/470749 for a full answer. – Ryan Apr 01 '21 at 13:48
  • How do you know for certain this won't cause the file to be deleted before it's fully downloaded? What if you have a very large file for instance? – Jespertheend Feb 17 '22 at 10:04
4

After some experimenting, it seems both Chrome and Safari are able to download a file of 2GB just fine when revoking right after clicking an element. And Firefox was able to download a file of 600MB before the browser started grinding to a halt.

This is what I used to download large files:

const a = document.createElement('a');
const buffer = new ArrayBuffer(2_000_000_000);
const view = new Uint8Array(buffer);
for(let i=0; i<view.length; i++) {
    view[i] = 255;
}
a.href = URL.createObjectURL(new Blob([buffer]));
a.download = 'some.dat';
a.click();
URL.revokeObjectURL(a.href);

The spec doesn't specifically mention aborting existing streams when revoking a url, so in theory doing it like this would be just fine.

However, to be safe I would either revoke the url after a few seconds using setTimeout(), or if the download is initiated from a specific screen, you can add logic to revoke it once the user navigates away from that screen.

Browsers also automatically revoke object urls once the last page of your domain is closed, so depending on your situation, not revoking urls at all might also be a viable solution.

Jespertheend
  • 1,814
  • 19
  • 27