Skip to content

DOMException is not defined; abort and timeout errors are plain Error instead of AbortError / TimeoutError #52

Closed

Description

@hassidan

Test I ran

node abort_errors.ts
slice run abort_errors.ts --allow-net=127.0.0.1:1
abort_errors.ts
import { setTimeout as sleep } from "node:timers/promises";
const show = async (k: string, f: () => unknown) => { let v; try { v = await Promise.race([Promise.resolve().then(f), new Promise((r) => setTimeout(() => r("NEVER SETTLED (2000 ms)"), 2000))]); } catch (e: any) { v = "THROWS " + e.name + ": " + String(e.message).slice(0, 100); } console.log(k.padEnd(52) + JSON.stringify(v)); };
(async () => {
  await show("typeof DOMException", () => typeof DOMException);
  const ts = AbortSignal.timeout(5); await sleep(20);
  await show("AbortSignal.timeout(5).reason  [name, code]", () => [ts.aborted, ts.reason?.name, ts.reason?.code]);
  const ac = new AbortController(); ac.abort();
  await show("controller.abort() default reason [name, code]", () => [ac.signal.reason?.name, ac.signal.reason?.code]);
  await show("signal.throwIfAborted() throws error named", () => { try { ac.signal.throwIfAborted(); return "no throw"; } catch (e: any) { return e.name; } });
  await show("timers/promises setTimeout aborted [name, code]", async () => { const ac2 = new AbortController(); const p = sleep(1000, "x", { signal: ac2.signal }).catch((e) => [e.name, e.code]); ac2.abort(); return p; });
  await show("fetch(url, {signal: aborted}) rejects with", () => { const ac3 = new AbortController(); ac3.abort(); return fetch("http://127.0.0.1:1/", { signal: ac3.signal }).then(() => "resolved", (e) => e.name); });
  await show("new DOMException('m','AbortError') [name, code]", () => { const e = new DOMException("m", "AbortError"); return [e.name, e.code, e instanceof Error]; });
})();

Result

Node v24.22.2:

typeof DOMException                                 "function"
AbortSignal.timeout(5).reason  [name, code]         [true,"TimeoutError",23]
controller.abort() default reason [name, code]      ["AbortError",20]
signal.throwIfAborted() throws error named          "AbortError"
timers/promises setTimeout aborted [name, code]     ["AbortError","ABORT_ERR"]
fetch(url, {signal: aborted}) rejects with          "AbortError"
new DOMException('m','AbortError') [name, code]     ["AbortError",20,true]

Slice v0.10.7:

typeof DOMException                                 "undefined"
AbortSignal.timeout(5).reason  [name, code]         [true,"Error",null]
controller.abort() default reason [name, code]      ["Error",null]
signal.throwIfAborted() throws error named          "Error"
timers/promises setTimeout aborted [name, code]     ["Error",null]
fetch(url, {signal: aborted}) rejects with          "Error"
new DOMException('m','AbortError') [name, code]     "THROWS ReferenceError: DOMException is not defined"

What is wrong

  • globalThis.DOMException does not exist (COMPATIBILITY.md, "Events & Signals": "…AbortSignal.any(signals), and DOMException").
  • Every abort path produces an error whose name is "Error": controller.abort()'s default reason, signal.throwIfAborted(), AbortSignal.timeout() (should be TimeoutError), timers/promises cancellation and an aborted fetch().

The usual way to tell a cancellation from a failure is if (err.name === "AbortError") return; (or err.code === "ABORT_ERR"). On Slice that check never matches, so cancelled requests are handled as real errors.

Environment

  • Slice v0.10.7, commit ea7c70c, built with cargo +1.96.0 build --release --locked --bin slice
  • Reference: Node.js v24.22.2 (same script, same machine)
  • Ubuntu 26.04, Linux 7.0, x86_64
  • The same output is produced by the v0.10.5 tag, so this is not a regression in 0.10.6/0.10.7.

Activity

  1. siktec-lab commented on Nov 11, 2026

    @siktec-lab
    Collaborator

    Addressed in #67 (�0.10.11): added the global DOMException constructor with W3C legacy error codes (ABORT_ERR = 20, TIMEOUT_ERR = 23, DATA_CLONE_ERR = 25, etc.), updated AbortSignal.abort(), AbortController#abort(), AbortSignal.timeout(), and etch() to produce DOMException instances (while preserving custom abort reasons), and updated
    ode:timers/promises abort rejections to attach
    ame = 'AbortError', code = 'ABORT_ERR', and cause = signal.reason.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions