8.1 Workers and fetch(keepalive): the superset transport
The keepalive flag's specification, the fetch-groups exemption that lets requests outlive their context, and the exact capability trade against sendBeacon.
Advanced
Workers cannot call
sendBeacon — that fact is now old news in this series. What replaces it is not a compromise but a different tool with a wider handle and a weaker promise:
fetch() with
keepalive: true. This page reads the flag's actual specification, traces the mechanism that lets requests outlive their context, and inventories exactly what the superset gains and gives up.
The flag, in the spec's own words
"A request has an associated boolean keepalive. Unless stated otherwise it is false. This can be used to allow the request to outlive the environment settings object, e.g., navigator.sendBeacon() and the HTML img element use this."— Fetch, Requests
The mechanism behind those fourteen words sits in the fetch groups algorithm: when a group is terminated — the document discarded, the worker stopped — every in-flight request's controller is terminated
unless its request's keepalive is true. That exemption is the entire survival trick, and it is the same one beacons rely on: a
sendBeacon request and a hand-rolled
fetch(keepalive) request are exempt from teardown by the same clause, draw from the same 64 KiB in-flight pool, and are indistinguishable to a service worker (as the detection page showed). The series' test suite verifies the worker side directly: inside a real dedicated worker,
navigator.sendBeacon is undefined,
fetch is present, and a
fetch(keepalive) POST reaches a route-intercepted collector intact.
The superset advantages
Capability | sendBeacon | fetch(keepalive) |
|---|---|---|
HTTP method | POST only, fixed | any method |
Headers | none — Content-Type is derived | any headers via
|
Response | never seen | full
|
Credentials | forced
| omit / same-origin / include |
Return signal | synchronous boolean | Promise |
Flush on hidden | MUST, per the Beacon spec | not required by any spec |
Shared 64 KiB pool | yes | yes — the same pool |
Read the last three rows together and the shape of every later decision appears: the two transports differ in signal, guarantee, and synchrony — not in capacity. A hybrid that falls back from one to the other is choosing a different contract, not a different quota.
The sender recipe, with response handling
The runnable example is the reference sender for the superset side: one async function with the three
RequestInit fields that matter, a result object mirroring the beacon's boolean shape (with status and body when available), and honest error routing — the stream-plus-keepalive
TypeError surfaces synchronously from
Request construction, while the quota refusal arrives later as a rejected promise. The injectable
fetch lets the success path run under this page's network-blocking sandbox:
Output
Ready to run.
Quick check
Inside a dedicated worker, which call can send telemetry that survives the worker's termination?
// inside a worker
Key takeaways
- keepalive exempts a request from fetch-group termination — the survival mechanism beacons and keepalive fetches share.
-
Verified in the series' suite: a real worker has no
sendBeacon, hasfetch, and its keepalive POST arrives intact. - The superset: any method, any headers, a real Response, chosen credentials — at the cost of the MUST-flush guarantee and the sync boolean.
-
Error modes: stream bodies throw at
Requestconstruction; quota refusals reject thefetchpromise with a TypeError. - Capacity is never the difference: both transports draw from the same 64 KiB in-flight pool.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.