{"id":23814477,"url":"https://github.com/stuartpb/fetchcontroller-spec","last_synced_at":"2026-02-15T01:01:51.974Z","repository":{"id":139971200,"uuid":"78863484","full_name":"stuartpb/FetchController-spec","owner":"stuartpb","description":"A preliminary document outlining extensions to window.fetch for control and observation","archived":false,"fork":false,"pushed_at":"2017-01-13T21:27:26.000Z","size":10,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-09-08T05:44:38.171Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stuartpb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2017-01-13T15:54:30.000Z","updated_at":"2019-07-26T10:30:16.000Z","dependencies_parsed_at":null,"dependency_job_id":"8f0e4cea-318a-4e2b-95dd-06470af6f384","html_url":"https://github.com/stuartpb/FetchController-spec","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/stuartpb/FetchController-spec","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stuartpb%2FFetchController-spec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stuartpb%2FFetchController-spec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stuartpb%2FFetchController-spec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stuartpb%2FFetchController-spec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stuartpb","download_url":"https://codeload.github.com/stuartpb/FetchController-spec/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stuartpb%2FFetchController-spec/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29463474,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-14T22:42:09.113Z","status":"ssl_error","status_checked_at":"2026-02-14T22:42:05.053Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-01-02T03:47:47.169Z","updated_at":"2026-02-15T01:01:51.952Z","avatar_url":"https://github.com/stuartpb.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# FetchController-spec\n\nA preliminary document outlining extensions to window.fetch for control and observation.\n\nThis is an attempt to consolidate most of the discussion in [whatwg/fetch#447][] one consistent actionable proposal.\n\n[whatwg/fetch#447]: https://github.com/whatwg/fetch/issues/447\n[whatwg/fetch#448]: https://github.com/whatwg/fetch/issues/448\n\nThis is the first time I've really taken a whack at writing a specification, so it's admittedly somewhat sloppy, and missing all the markdown of what sections are \"normative\" and [what sections are \"informative\"](https://twitter.com/geddski/status/814296358714605568) and all that fancy jazz that a proper WHATWG document would have.\n\n## Terminology\n\nFor the purposes of this document, \"a fetch\" refers to all actions entailed with an in-flight request by the user agent, as instantiated by `fetch()` (or the browser plumbing as handed to a Service Worker via the `fetch` event), including the user agent's processing and handling of that request's response. (See also the preface to [whatwg/fetch#448][].)\n\n## Constructed-or-revealing-constructor options\n\nA recurring pattern in this specification. which I generally spell out in longer terms instead of referring to it by name (as I haven't given it a pithy one), is an option on a call/constructor that may take either a function that works as a revealing constructor (ie. an object is constructed and then passed to the given callback), or an object that has *already* been constructed (ie. by calling its constructor directly):\n\n```js\n// this is fine...\nfunction useRevealedController(url) {\n  return fetch({url, controller: controller =\u003e {\n    /* use `controller` however you want here */\n  }});\n}\n\n// and this is fine, too!\nfunction usePreconstructedController(url) {\n  let controller = new FetchController();\n\n  /* use `controller` however you want here */\n\n  return fetch({url, controller});\n}\n```\n\nThis design pattern is in place to address developer sentiments that one flow would be less natural with the way their code is factored over another. Accomodating both of these flows doesn't cause any conflicts in the specification's design, and, indeed, respecting *both* of them, *cooperatively*, allows for better factoring of implementations to *interoperate* according to whichever form factor makes more sense for their implementation. For example, there are many points in this specification describing behaviors that should be followed when a function call employs *both* approaches *simultaneously*, eg. preconstructing a `FetchObserver` and passing it for use with a to-be-revealed `FetchController`:\n\n```js\n// maybe this makes the most sense for your use case!\nfunction useRevealedControllerWithPreconstructedObserver(url) {\n  let observer = new FetchObserver();\n  return fetch({url, observer, controller: controller =\u003e {\n    /* use `controller` however you want here -\n       its `controller.observer` is the observer you passed in! */\n  }});\n}\n```\n\n## Two new classes\n\nThis proposal adds two new classes, `FetchController` and `FetchObserver`.\n\n### `FetchObserver`\n\nThe `FetchObserver` class provides passive properties and methods to *observe the state* of a fetch, from its instantiation, through any upload and download traffic, up until the fetch's completion.\n\n`FetchObserver` features an `addEventListener` method to listen to events from a fetch, such as `upload` and `download` (for respective progress events), `response` (for when response headers have been received), and `end` (for when the fetch has ended).\n\n### `FetchController`\n\n`FetchController` features methods to control a fetch, such as `abort()` to abort the fetch (rejecting any related promises pending from it). It also includes an `.observer` property, containing an associated `FetchObserver` for monitoring the fetch (which may be constructed before the `FetchController` and associated on the `FetchController` object's construction, as described below).\n\n## `FetchEvent.observer`\n\n`FetchEvent` objects passed to Service Workers' `fetch` event listeners will include a read-only `FetchObserver` property that allows the lifecycle of the fetch to be observed (from the perspective of the client initiating the fetch being handled: see notes on \"`FetchObserver` events\" below).\n\n## Two new `fetch()` options\n\nThe `window.fetch()` function's dictionary of options recognizes two new properties, `controller` and `observer`. A call to `fetch()` may specify none, one, or both of these properties, with either/both accepting either a pre-constructed object (using the constructors described below), or a revealing-constructor callback function (which will receive a constructed object associated with the fetch).\n\nTo be clear, these are the internal steps `fetch()` follows:\n\n- Let \"the fetch\" be the fetch actions that a Controller and/or Observer will be associated with.\n- If there is a value defined for the `observer` option:\n  - If the `observer` option is a pre-constructed `FetchObserver` object:\n    - If the `controller` option is a pre-constructed `FetchController` object, and the `.observer` property of that controller is not the `FetchObserver` defined by the `observer` option, throw a TypeError.\n    - Else, if the pre-constructed `FetchObserver` is already associated with a fetch (`.pending` is `false`), throw a TypeError.\n    - Else, let \"the observer\" be that `FetchObserver`.\n  - Else, if the `observer` option is a function, let \"the observer\" be a newly-constructed `FetchObserver`.\n  - Else, throw a TypeError.\n- If there is a value defined for the `controller` option:\n  - If the `controller` option is a pre-constructed `FetchController` object:\n    - If the pre-constructed `FetchController` is already associated with a fetch (`.pending` is `false`), throw a TypeError.\n    - Else, if the pre-constructed `FetchController` has a constructed `FetchObserver` as its `.observer` that is already associated with a fetch (`.pending` is `false`), throw a TypeError.\n    - Else, let \"the controller\" be that `FetchController`.\n  - Else, if the `controller` option is a function:\n    - If \"the observer\" has already been defined, then let \"the controller\" be a newly-constructed `FetchController` with \"the observer\" as its initial `.observer` property.\n    - Else, let \"the controller\" be a newly-constructed `FetchController`, let \"the observer\" be an implicitly-constructed observer constructed with \"the controller\" as its initial `.observer` property.\n  - Else, throw a TypeError.\n- Associate \"the controller\" and \"the observer\", if either are present, with \"the fetch\".\n- If there is a function present as the `observer` option, call it with \"the observer\" as its argument.\n- If there is a function present as the `controller` option, call it with \"the controller\" as its argument.\n- Perform \"the fetch\", as defined in the steps of https://fetch.spec.whatwg.org/#fetching\n\nWhile I've refrained from using this kind of \"actual steps\" dissection in the rest of this loose proposal, the steps above were the clearest way I could think to explain that \"revealing constructors\" in `fetch()` are actually called *after the objects in question have been constructed*, specifically so that they are associated with the fetch *before the callbacks* (to avoid footguns like code thinking it can associate the controller with a newly-instantiated fetch *within the revealing constructor callback*).\n\n## Class constructors\n\nThe `FetchController` and `FetchObserver` classes may be constructed directly by calling their respective contructors.\n\n### `new FetchObserver(options)`\n\nConstructs a new `FetchObserver`, to be associated with a corresponding fetch and/or `FetchController` at some point after the `FetchObserver` object's construction.\n\nThe `options` object is only reserved for the purposes of future extension. This docuemnt specifies no options for the `FetchObserver` constructor at this time. (Note that any options that would be passed to this constructor, for the purposes of parity with revealing-constructor use cases, should only be considered if they may be mirrored as an option to `fetch()` and the standalone `new FetchController(options)` constructor.)\n\n### `new FetchController(options)`\n\nConstructs a new `FetchController`, for use with a fetch at some point after construction.\n\nThe only `options` property specified at this time is `observer`, which may take either a pre-constructed `FetchObserver` and associate it with the new `FetchController` as its `.observer` property, or a revealing-constructor function that will receive the newly-constructed `FetchObserver` associated with the `FetchController` as its `.observer` property. (A new `FetchObserver` will be constructed for any constructed `FetchController`, except when an existing one is passed in.)\n\nIf a FetchObserver that has already been associated with a fetch (`.pending` is `false`) is passed to this constructor, this will throw a TypeError.\n\n## Properties and methods\n\n### FetchController.observer\n\nA `FetchObserver` for the fetch controlled by this `FetchController`.\n\nOnce the `FetchController` is associated with a fetch, this property is effectively read-only: any attempt to assign a different value to it (other than the `FetchObserver` associated with the same fetch it already has) will result in a TypeError.\n\nWhile this property may be written to (so long as the `FetchController` has not been associated with a fetch), if the value being set is anything other than a `FetchObserver` with a `.pending` value of true (one that has yet to be associated with a fetch), the assignment will throw a TypeError. (Also, as the value is always initialized to a `FetchObserver` on construction, there should never be a need to set this.)\n\n### FetchObserver.pending, FetchController.pending\n\nRead-only property. A boolean introspecting whether the `FetchController` or `FetchObserver` is associated with a fetch (`false` if associated, `true` if not).\n\nThis boolean is mostly only meaningful in the context of a pre-constructed `FetchController` or `FetchObserver` (for determining whether it has been used or not); for `FetchController` or `FetchObserver` objects constructed via revealing constructor or provided with a `FetchEvent`, this will always be `false`.\n\nAs corresponding one-time Promise properties are on the table, design-wise, for these objects, neither a `FetchController` nor a `FetchObserver` may be reused for multiple fetches, nor may a `FetchController` or `FetchObserver` have their associated fetch changed (or vice versa) after the fetch's instantiation. As such, this property helps end developers determine whether or not a `FetchController` or `FetchObserver` is safe to use with a new fetch.\n\n### FetchObserver.finished\n\nRead-only property describing whether, and how, the fetch associated with this `FetchObserver` terminated:\n\n- If the fetch has not finished (is still waiting, or if the `FetchObserver` has not been associated with a Fetch yet), this is `false`.\n- If the fetch has completed successfully, this is `\"complete\"`.\n- If the fetch was aborted, this is `\"abort\"`.\n- If the fetch ended in an error, this is `\"error\"`.\n\n### FetchObserver.request\n\nRead-only property. The `Request` that instantiated the fetch that this `FetchObserver` observes.\n\nWhen the `FetchObserver` is a property of a `FetchEvent` alongside `FetchEvent.request`, `FetchObserver.request` is admittedly redundant: however, it's a sensible property to allow code that *only takes `FetchObserver` arguments* to operate normally, as well as making the request readable to `FetchController` objects.\n\n### FetchController.fetch(options)\n\nPer [this comment](https://github.com/whatwg/fetch/issues/447#issuecomment-270739989).\n\nIf a `FetchController` has been constructed and is pending (see `FetchController.pending` above), this performs a `fetch()` with all the given `options` as `window.fetch()` would, using the `FetchController` as the fetch's controller.\n\nIf a function is specified for the `controller` or `observer` options (ie. \"revealing constructors\"), they are passed the already-constructed `FetchController` or corresponding `.observer`, respectively. If any other value is defined for either these options, the fetch fails with a `TypeError` (other than the `FetchObserver` that is already the `.observer` property of the `FetchObserver`), per the `fetch()` steps described above.\n\nIf the `FetchController` has already been associated with a fetch (`.pending is false`), this throws a TypeError.\n\nNote that, although such a method *could* be specified, this document *does not* specify a corresponding `FetchObserver.fetch()` method, as methods on `FetchObserver` are meant to be uniformly *passive*.\n\n### FetchController.abort()\n\nAborts the corresponding fetch, causing any pending promises on the fetch to be rejected with an `AbortError`.\n\nIf the `FetchController` has been constructed without an associated fetch and is still pending (see `FetchController.pending` above), this is an error.\n\nIf the fetch is already complete, this is an error. See [whatwg/fetch#448][].\n\n## FetchObserver events\n\nThese are patterned after [the progress events of XMLHttpRequest][Monitoring progress], but with the names changed, to avoid the ironically-overloaded use of the word \"load\", and to consolidate upload and download events into the same stream (rather than having all but one kind of event mirrored across two objects, with the one differing kind of event having the same name).\n\n[Monitoring progress]: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Monitoring_progress\n\nNote that, for a `FetchObserver` provided as part of a `FetchEvent` for a Service Worker, these events correspond to the point of view of fetch *being handled*: for instance, the `progressdown` events will fire for whatever data the Service Worker provides via `respondWith`, *even if that data is being produced by code within the Service Worker from a non-network source*.\n\n### `start`\n\nFired when the fetch's request is initiated. This is analogous to XHR's \"loadstart\" event.\n\n### `upload`\n\nFired when the fetch sends data in the request. This is analogous to the \"progress\" event on an XMLHttpRequest's `upload` object.\n\n### `response`\n\nFired when the fetch receives a response and headers from the server. This is analogous to the \"readystatechange\" event when an XMLHttpRequest's `readyState` changes to `2` (`HEADERS_RECEIVED`).\n\n### `download`\n\nFired when the fetch receives data in the response. This is analogous to the \"progress\" event on an XMLHttpRequest's *base* object.\n\n### `complete`\n\nFires when the fetch completes successfully. This is analogous to XHR's \"load\" event.\n\n### `abort`\n\nFires if the fetch is aborted, either by a `FetchController` or by the user agent. (More data about the source of the abort will probably be available on the event.) This is analogous to XHR's \"abort\" event.\n\n### `error`\n\nFires if the fetch fails, for whatever non-abortive reason (such as if the connection is unexpectedly terminated). This is analogous to XHR's \"error\" event.\n\n### `end`\n\nFires after `complete`, `abort`, or `error`. This is analogous to XHR's \"loadend\" event.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstuartpb%2Ffetchcontroller-spec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstuartpb%2Ffetchcontroller-spec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstuartpb%2Ffetchcontroller-spec/lists"}