denoland/deno · error · NodeError
ERR_EVENT_RECURSION
ERR_EVENT_RECURSION
Error message
The event "${event.type}" is already being dispatched What it means
Node keeps a [kIsBeingDispatched] flag on each event; dispatchEvent refuses any event whose flag is still set, throwing ERR_EVENT_RECURSION. This happens when the exact same Event instance is re-dispatched synchronously while its own listeners are still running. Dispatching a fresh event with the same type is always legal.
Source
Thrown at ext/node/polyfills/internal/event_target.mjs:708
}
handler = handler.next;
}
}
/**
* @param {Event} event
*/
dispatchEvent(event) {
if (!isEventTarget(this)) {
throw new ERR_INVALID_THIS("EventTarget");
}
if (!ObjectPrototypeIsPrototypeOf(globalThis.Event.prototype, event)) {
throw new ERR_INVALID_ARG_TYPE("event", "Event", event);
}
if (event[kIsBeingDispatched]) {
throw new ERR_EVENT_RECURSION(event.type);
}
this[kHybridDispatch](event, event.type, event);
return event.defaultPrevented !== true;
}
[kHybridDispatch](nodeValue, type, event) {
const createEvent = () => {
if (event === undefined) {
event = this[kCreateEvent](nodeValue, type);
event[kTarget] = this;
event[kIsBeingDispatched] = true;
}
return event;
};
if (event !== undefined) {
event[kTarget] = this;View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Dispatch a fresh instance: dispatchEvent(new Event(e.type)) instead of e
- For CustomEvent payloads: dispatchEvent(new CustomEvent(e.type, { detail: e.detail }))
- If re-dispatch must happen in the listener, defer it: queueMicrotask(() => target.dispatchEvent(new Event(e.type)))
- Audit listeners that forward the event they received into another dispatchEvent
Example fix
// before
target.addEventListener('tick', (e) => {
target.dispatchEvent(e); // same instance, still dispatching
});
// after
target.addEventListener('tick', (e) => {
target.dispatchEvent(new CustomEvent(e.type, { detail: e.detail }));
}); Defensive patterns
Strategy: validation
Validate before calling
function safeDispatch(target, type, detail) {
// always build a fresh event: a cached/in-flight instance would throw ERR_EVENT_RECURSION
const ev = detail === undefined ? new Event(type) : new CustomEvent(type, { detail });
target.dispatchEvent(ev);
} Try / catch
try { target.dispatchEvent(e); } catch (err) { if (err?.code === 'ERR_EVENT_RECURSION') { queueMicrotask(() => target.dispatchEvent(new Event(e.type))); return; } throw err; } Prevention
- Never reuse an Event instance across dispatches; treat events as single-shot
- In listeners that re-fire, construct a new event with the same type and copied detail
- Defer re-dispatch with queueMicrotask/setTimeout if ordering matters
When it happens
Trigger: Inside a listener for event e, calling target.dispatchEvent(e) with the same object; replay utilities that cache the last real event and re-dispatch the cached instance; retry logic inside a listener that re-fires the triggering event; forwarding pipelines that pass the received event object to another dispatchEvent call.
Common situations: Event-replay and mocking helpers that store live events; middleware that wraps dispatchEvent and re-dispatches the same instance; one CustomEvent object hoisted to module scope and reused for every emit.
Related errors
- ERR_INVALID_ARG_TYPE
- Empty filepath.
- resolve hook must return { shortCircuit: true } or call next
- load hook must return { shortCircuit: true } or call nextLoa
- Unknown built-in module
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/9a6c8ffbd729d24a.
Report an issue: GitHub.