Skip to content

fetch/AbortSignal: honor init.signal + modernize AbortSignal — hop 4 of #196#202

Closed
bkaradzic-microsoft wants to merge 1 commit into
BabylonJS:mainfrom
bkaradzic-microsoft:hop4-abort-signal
Closed

fetch/AbortSignal: honor init.signal + modernize AbortSignal — hop 4 of #196#202
bkaradzic-microsoft wants to merge 1 commit into
BabylonJS:mainfrom
bkaradzic-microsoft:hop4-abort-signal

Conversation

@bkaradzic-microsoft

Copy link
Copy Markdown
Member

Hop 4 of #196 — make AbortController / init.signal actually work in fetch(), and modernize the AbortSignal polyfill.

Problem

fetch() passed arcana::cancellation::none() for both continuations and never looked at init.signal, so abort never rejected (AbortError) and never cancelled the transport. The AbortSignal polyfill also predated the modern spec — no reason, no throwIfAborted(), a writable aborted, and no static AbortSignal.abort(). Combined with AbortController being installed globally in the Playground (BabylonJS/BabylonNative#1708), library feature-detection passed while signals silently no-op'd — making user cancellations indistinguishable from network failures in telemetry.

AbortSignal

  • reason (read-only) and throwIfAborted().
  • static AbortSignal.abort(reason?).
  • aborted is now read-only (setter removed).
  • Abort(reason) records the reason, defaulting to an AbortError — an Error whose name is "AbortError" (there is no DOMException polyfill, so this matches what web code checks: err.name === "AbortError").
  • AbortController.abort(reason?) forwards the reason to the signal.

fetch

  • Honors init.signal through the signal's JS interface (aborted / reason / add/removeEventListener), so fetch stays decoupled from the AbortController polyfill's C++ types.
  • An already-aborted signal rejects the promise synchronously with the signal's reason, never touching the transport.
  • Otherwise an "abort" listener cancels the in-flight transport via UrlRequest::Abort(), and the completion continuation rejects with the signal's reason (an AbortError) instead of a network-error TypeError. The listener is torn down when the request settles (breaking the listener ⟷ state ownership cycle).

Validation

Built and ran the UnitTests suite on Win32 / Chakra — all pass (199), including new tests:

  • AbortSignal.abort() returns an aborted signal whose reason is an AbortError; throwIfAborted() throws only once aborted; abort(reason) records the reason; abort() defaults to an AbortError.
  • fetch rejects with an AbortError both when the signal is already aborted and when aborted in-flight (the in-flight case cancels the WinRT transport and settles in ~50 ms).

Win32's UrlLib backend honors UrlRequest::Abort(), so the end-to-end abort path is exercised here.

Scope / follow-ups

Part of the #196 chain (hops 2 and 3 are #200 and #201).

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

fetch() previously ignored init.signal entirely (it passed
arcana::cancellation::none() to both continuations), so AbortController could
never cancel a request or reject with AbortError, and the AbortSignal polyfill
predated the modern spec. This is hop 4 of BabylonJS#196.

AbortSignal:
- Add `reason` (read-only) and `throwIfAborted()`.
- Add static `AbortSignal.abort(reason?)`.
- Make `aborted` read-only (remove the setter).
- Abort(reason) now records the reason, defaulting to an AbortError (an Error
  whose name is "AbortError", as there is no DOMException polyfill).
- AbortController.abort(reason) forwards the reason to the signal.

fetch:
- Honor init.signal via its JS interface (so fetch stays decoupled from the
  AbortController C++ types): an already-aborted signal rejects synchronously
  with the signal's reason; otherwise an "abort" listener cancels the transport
  (UrlRequest::Abort()) and the completion continuation rejects with the
  signal's reason (an AbortError) instead of a network-error TypeError. The
  listener is torn down when the request settles, breaking the ownership cycle.

Transport cancellation is effective on backends where UrlRequest::Abort() is
implemented (Windows today; the curl/NSURLSession backends are a UrlLib
follow-up noted in BabylonJS#196). AbortSignal.timeout() is left as a follow-up.

Adds JS tests for the modern AbortSignal API and for fetch rejecting with an
AbortError both pre-aborted and in-flight (validated on Win32, where the UrlLib
backend honors Abort()).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@bkaradzic-microsoft

Copy link
Copy Markdown
Member Author

Consolidated into #200 — the whole #196 chain is now a single PR (hop 4 is the last commit there). Closing to avoid three PRs for one feature in one repo, and because the AbortSignal/fetch changes share Fetch.cpp with hop 2 (now integrated cleanly in #200).

@bkaradzic-microsoft bkaradzic-microsoft deleted the hop4-abort-signal branch June 16, 2026 23:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants