JavaScript File API Table
| Piece | What it does | Field note |
|---|---|---|
input.files / dataTransfer.files | Get File objects | File = Blob + name/lastModified - the metadata arrives free |
file.text() | Read as text, promise | The modern one-liner - no FileReader event dance |
file.arrayBuffer() | Read as bytes | Feeds typed arrays, hashing, uploads - the binary path |
FileReader.readAsDataURL | Base64 embed | Small previews only - 33% size tax on every byte |
createObjectURL(file) | Zero-copy reference | A URL POINTING at the file - revokeObjectURL or leak memory |
file.slice(start, end) | Partial reads | Big file chunking - read the header, hash in pieces |
type & size BEFORE read | Validate first | MIME is client-claimed - the server re-checks everything |
showOpenFilePicker | The newer handle API | Re-readable handles without re-prompting - check support |
File objects are Blobs with a name: they arrive from an input or a drop carrying size, type and lastModified readable BEFORE any content loads - validation is free, reading is a choice. The modern reads are promise one-liners: file.text() and file.arrayBuffer() replaced the FileReader event dance.
Bottom line: createObjectURL(file) is the zero-copy preview path - it hands you a URL that POINTS at the file's bytes without reading them, which is also why it leaks until you call revokeObjectURL. And the client-side MIME type is a claim, not a fact: the server re-checks everything.
The honest part: FileReader is not dead - it owns progress events (the upload/parse progress bar) and readAsDataURL for tiny inline previews. But its callback-and-event shape loses to promises everywhere else, and the 33% base64 size tax on dataURLs makes them wrong for anything over an icon.
How to use
- Validate before reading: check file.size and file.type against limits first - metadata is free, and a 4GB surprise should stop before the read starts.
- Read with promises: const text = await file.text() for config and logs; const buf = await file.arrayBuffer() for hashing, parsing or typed arrays.
- Preview with object URLs: const url = URL.createObjectURL(file); img.src = url; then URL.revokeObjectURL(url) after load - zero-copy, unlike the base64 detour.
Frequently asked questions
When is file.text() better than FileReader.readAsText?
Almost always in modern code: both produce the file's text, but text() is a promise that composes with async/await and error handling, while FileReader demands load/error/progress event wiring in callback style. FileReader keeps two genuine niches: progress events (a reading-progress bar over a large file - promises report only completion) and readAsDataURL, which no File method replicates. The refactoring rule: new code uses text()/arrayBuffer() by default and reaches for FileReader only when the progress bar or the data URL is the actual feature.
Why does createObjectURL need revokeObjectURL, and when exactly do I call it?
The URL holds a live reference. createObjectURL hands out a blob: URL that keeps the file's bytes alive in memory as long as the URL exists - which is the point (the img can load it lazily) and the leak (nothing auto-revokes; a single-page app creating URLs per upload accumulates every file ever selected until reload). The pattern: revoke after the consumer finishes - img.onload is the classic moment for previews, component teardown for longer-lived usage. Data URLs, by contrast, are self-contained strings (no revoke needed) at a 33% size tax - which is why the split is: object URLs for real files, data URLs for tiny inline embeds.
How does file.slice() enable handling gigabyte files?
Blobs support partial views without loading: file.slice(0, 1024) is a new Blob over those bytes, read on demand. Chunked processing follows - read 8MB pieces in a loop, hash each piece incrementally (resumable uploads are built exactly this way: per-chunk checksums, retry only failed chunks), or parse just a file header to identify the type before committing to the rest. Memory stays bounded because only one chunk is in RAM at a time. The same slice powers Range-style uploads and the difference between a tool that handles a 5GB video and one that dies on it.
Why is checking file.type not a security measure?
Because the client controls the claim. The MIME type arrives from the browser based on the file's extension and OS registration - renaming virus.exe to report.pdf yields type 'application/pdf' - so client-side type checks are UX (friendly early errors), not security. The real checks live server-side: inspect magic bytes (PDF starts with %PDF, PNG with its signature), re-scan content, and serve uploads from a separate origin with Content-Disposition so stored payloads cannot execute in your origin. Client validation's honest role: stop honest users from honest mistakes, fast - before the multi-megabyte upload starts.