◆ タイトル通り
◆ ダウンロードが終わるのを待ってから URL.revokeObjectURL を呼び出す必要はない

URL の作成と取り消し

DataURI 形式だと許容されている文字列の長さの限界の影響を受けることが多いので ある程度大きなファイルを想定してダウンロードするときには Blob にして URL.createObjectURL を使って URL を作成しそこからダウンロードをしています

const downloadBlob = (blob, filename) => {
const url = URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = filename
a.click()
}

これでダウンロードができますが 作った URL が残っています
ページを閉じれば消えるので すぐページ遷移するようなページならこれでいいかもしれません
しかし SPA など長期間ページ遷移をしない場合は いつまでも残っています
ファイルが大きかったりいくつもダウンロードする場合はメモリの無駄遣いです
なので 通常は URL.revokeObjectURL を使って作成した URL を取り消します

URL.revokeObjectURL(url)

タイミング

この取消のタイミングっていつがいいんでしょう?

昔はダウンロードが終わるまでは消えたらまずいかなと 適当なディレイを挟んでいました

const downloadBlob = (blob, filename) => {
const url = URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = filename
a.click()
setTimeout(() => {
URL.revokeObjectURL(url)
}, 5000)
}

汎用的なダウンロード関数として作る場合 大きなファイルも考慮したいので setTimeout の待機時間を blob のサイズを見て可変にしたりもしました

ただこれ本当にいるの?と思って setTimeout を外してみると特に問題なく動いていました
それ以降はずっとこんな感じです

const downloadBlob = (blob, filename) => {
const url = URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = filename
a.click()
URL.revokeObjectURL(url)
}

本当に大丈夫?

動いてるしで使ってましたが 本当に大丈夫なのか使うたびに気にはなってました
最近の環境は SSD 前提で書き込み速度が十分に速いのに加えて Blob データはすでにメモリ内にあるので数 MB 程度の書き込みは一瞬です
URL が無効化されるまでに書き込まれてるから大丈夫なのであって そうでない場合は失敗したりはしないのでしょうか

ハードディスクの環境があればよかったのですが 手元にある PC は全部 SSD です
ストレージの書き込み速度を制限できないかググっても良さそうな方法が見当たらないです
仕方ないのでダウンロード先をネットワーク越しのストレージに切り替えることにしました

WSL が入ってる PC だったので 別 PC は使わず PC 内の WSL ファイルシステムを指定します
「\\wsl.localhost\Ubuntu-22.04\home\user」 みたいなフォルダをブラウザのダウンロード先に設定します

確認用に簡単なページを作りました

<!doctype html>

<button id="create">create</button>
<button id="download">(download)</button>

<script>
const wait = ms => new Promise(r => setTimeout(r, ms))
let url = ""

create.onclick = async () => {
const buf = new ArrayBuffer(1024*1024*1024*1)
const blob = new Blob([buf])
url = URL.createObjectURL(blob)
download.textContent = url
}

download.onclick = () => {
const a = document.createElement("a")
a.href = url
a.download = "data.bin"
a.click()
URL.revokeObjectURL(url)
download.textContent = "(download)"
}
</script>

create を押すと 1GiB の Blob を作りその URL を準備します
download を押すとダウンロードを開始し 即時 revoke します

ダウンロードの完了まで数秒かかりましたが 途中でエラーが出ることなく完了しました
WSL 側でファイルサイズを確認してみると期待通りの 1G となっていました

すぐに revokeObjectURL して大丈夫そうです

仕様的に

Chrome/Edge で大丈夫だとわかりましたが 仕様的には保証されてるのでしょうか

MDN ではそういうケースについての記載はなさそうだったので FileAPI の仕様書を見てみます
https://www.w3.org/TR/FileAPI/#creating-revoking

revoke されたあとにアクセスがあるとネットワークエラーだけど revoke 前に開始されたリクエストは成功する
と書かれてました

つまり ダウンロード開始直後に revoke して大丈夫 ということですね
これからは安心して即 revoke していけます

扱い的には revoke しても URL が無効化されるだけで Blob は残っていると考えて良さそうですね
URL が有効な間は URL からの参照があるのでずっと残っていて URL が無効化されてもリクエストのレスポンスを返してる間はまだその参照があるので残っていて レスポンスを返し終わると完全に参照がないので UA が適当なタイミングで GC して Blob が消えるという感じでしょうか