Skip to content

Instantly share code, notes, and snippets.

@amn
Created June 14, 2025 11:07
Show Gist options
  • Save amn/02ed560f93b1341512e8d302bf4ebf7a to your computer and use it in GitHub Desktop.
Save amn/02ed560f93b1341512e8d302bf4ebf7a to your computer and use it in GitHub Desktop.

In my opinion, there are some less than ideal properties to the Fetch API -- in particular the "cloning" of Request objects. This plays a role when you want to duplicate requests for re-sending. Most people may not notice the problem because unless a Request is initialized with [non-null] body, through the Request constructor or the fetch procedure, it's a non issue.

For requests with a body, however, the problem boils down to the fact that you can only clone a Request, using clone, if the former uses a body, a stream, which has not been "disturbed". In other words the stream must be "unused" or "pristine" (never read from), for clone to succeed, otherwise it will abort with an exception. Fair enough -- you can just clone early, right? Well, there's more to it: Request objects may be constructed with an init argument, and the latter may also be used with fetch. The argument also allows to specify a body, thus effectively overriding whatever body was set on the first argument to the Request constructor or fetch, input as it is called. And for init specifically, you can't just "clone" it -- clone is a method of Request and it accepts no parameters -- it simply clones the Request (and to reiterate again, with the caveat that the body stream, if set, is "undisturbed").

So how do you duplicate requests that are effectively specified with input and init, where init may define a stream for body. That's the crux of it -- that more code is needed, beyond what clone gives.

In context of the gist, it's really the problem of not being able to rely on just doing new Request(input, init).headers for accessing headers that are implied in a pair of input and init -- if there is some body on either one of the arguments, which may be a stream (body of a Request is always a stream, even if init body allows for other kinds of values -- the former is, well, initialized from the value), then you get an unusable stream -- you've essentially "used up" input or init (specifically the stream on whichever one of these it was available on, with init taking priority).

This is why e.g. headers function in the gist was written -- sometimes you just want to know what headers apply for a request expressed with a pair of input and init, since the former expression is problematic as described above.

There is a generalization of headers (the function in the gist) -- it's the request_without_body function that "idempotently" constructs an actual Request that is equivalent (in terms of e.g. "sending" with fetch with the notable exception that there's no body to it) to the pair of input and init [it is constructed from], including for accessing the headers of the request. To the best of my current understanding, that is -- there may be other mutable types besides the ReadableStream for body, but I don't think there are. When I have the time, I should look closer into the specification of Request constructor, so that I may be certain the request_without_body implementation in the gist is sound.

/**
* Some useful HTTP-related Web API helpers.
*
* See:
* * https://fetch.spec.whatwg.org/
*/
/**
* Compile and return a `Headers` object corresponding to well-known objects `input` and `init` that normally are passed to `Request` constructor.
*
* Why this function? `Request` objects may be non-trivial to construct -- for purposes of just accessing headers that are inherent in a pair of arbitrary values `input` and `init` that are passed to `Request` (the constructor). Furthermore, constructing a `Request` where a `ReadableStream` is referenced with the `body` property either on `input` or on `init`, "disturbs" the stream making it unusable (the constructed `Request` will use a usable copy). All these factors make this function an attractive proposition. See also `request_without_body` for an alternative that does use `Request` constructor but does not disturb the `body` stream.
*
* @param input See https://fetch.spec.whatwg.org/#dom-request-request-input-init-input
* @param init See https://fetch.spec.whatwg.org/#dom-request-request-input-init-init
* @returns {Headers} The corresponding headers; should be the same set as `Request(input, init).headers`
*/
const headers = (input, init) => {
const { headers } = (init?.headers === undefined) ? resource : init;
return (headers instanceof Headers) ? headers : new Headers(headers);
};
/**
* Construct an equivalent request but without using a `body` that would otherwise apply.
*
* Not using the `body` makes this a "safe" operation -- no streams are disturbed and `request_without_body` may be called on the same pair of values over and over again, with no side-effects.
*/
const request_without_body = (input, { body, ...init }) => new Request(input, init);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment