denoland/deno · error · DOMException
InvalidStateError
InvalidStateError
Error message
Invalid FileReader state
What it means
InvalidStateError DOMException thrown at the top of FileReader's #readOperation (ext/web/10_filereader.js), implementing the spec's step 1: if the FileReader's state is already "loading" — a read is in flight — starting another read is rejected. Each FileReader performs one read at a time; readiness is observable via the readyState property (EMPTY=0, LOADING=1, DONE=2).
Source
Thrown at ext/web/10_filereader.js:76
class FileReader extends EventTarget {
/** @type {"empty" | "loading" | "done"} */
[state] = "empty";
/** @type {null | string | ArrayBuffer} */
[result] = null;
/** @type {null | DOMException} */
[error] = null;
/** @type {null | {aborted: boolean}} */
[aborted] = null;
/**
* @param {Blob} blob
* @param {{kind: "ArrayBuffer" | "Text" | "DataUrl" | "BinaryString", encoding?: string}} readtype
*/
#readOperation(blob, readtype) {
// 1. If fr's state is "loading", throw an InvalidStateError DOMException.
if (this[state] === "loading") {
throw new DOMException(
"Invalid FileReader state",
"InvalidStateError",
);
}
// 2. Set fr's state to "loading".
this[state] = "loading";
// 3. Set fr's result to null.
this[result] = null;
// 4. Set fr's error to null.
this[error] = null;
// We set this[aborted] to a new object, and keep track of it in a
// separate variable, so if a new read operation starts while there are
// remaining tasks from a previous aborted operation, the new operation
// will run while the tasks from the previous one are still aborted.
const abortedState = this[aborted] = { aborted: false };
// 5. Let stream be the result of calling get stream on blob.View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Await completion before the next read: listen for loadend (or wrap the read in a Promise resolving on loadend/onerror/onabort) and chain reads sequentially.
- Use a new FileReader per read — they are cheap and stateless between reads.
- Prefer the promise APIs when possible: await blob.text(), blob.arrayBuffer(), or blob.data URL equivalents instead of FileReader.
- Guard with if (reader.readyState !== FileReader.LOADING) before calling readAs*.
Example fix
// before
for (const f of files) reader.readAsText(f); // 2nd call throws: still loading
// after
function read(file) {
return new Promise((res, rej) => {
reader.onload = () => res(reader.result);
reader.onerror = () => rej(reader.error);
reader.readAsText(file);
});
}
for (const f of files) await read(f); // sequential, always EMPTY when started Defensive patterns
Strategy: validation
Validate before calling
// readyState: EMPTY(0) LOADING(1) DONE(2) — only start when EMPTY/DONE.
function readText(reader, blob) {
if (reader.readyState === FileReader.LOADING) {
return Promise.reject(new Error('FileReader busy'));
}
return new Promise((res, rej) => {
reader.onload = () => res(reader.result);
reader.onerror = () => rej(reader.error);
reader.readAsText(blob);
});
} Type guard
function fileReaderIdle(reader) {
return reader.readyState !== FileReader.LOADING;
} Try / catch
try {
reader.readAsText(blob);
} catch (err) {
if (err instanceof DOMException && err.name === 'InvalidStateError') {
return queueReadAfterLoadend(reader, blob); // retry once idle
}
throw err;
} Prevention
- Use one FileReader per read, or serialize reads behind a loadend event.
- Prefer await blob.text()/blob.arrayBuffer() over FileReader in Deno.
- Wrap readAs* in a promise that resolves on loadend before issuing the next read.
When it happens
Trigger: Calling readAsText/readAsArrayBuffer/readAsDataURL/readAsBinaryString while a previous readAs* on the same FileReader has not fired loadend yet — e.g. issuing reads in a loop without awaiting completion, or firing a second read from a click handler while the first is still running.
Common situations: Loops that read a list of files with one shared FileReader; UI handlers that re-trigger on double-click before the first read finishes; code migrated from callback style where each read assumed a fresh reader.
Related errors
- ABORT_ERR
- ERR_INVALID_ARG_TYPE
- The "${name}" argument must be of type function. Received ${
- InvalidStateError
- Illegal constructor
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/76ade52ef7eae869.
Report an issue: GitHub.