Goal
Introduce new standard Symbols to denote the identity of Fetch API primitives:
Symbol.Request
Symbol.Response
Symbol.Headers
Motivation
The current Fetch API specification doesn’t define any criteria to use for the validation of Fetch API primitives: how can I be sure that a given object is a valid Request per Fetch API specification? While it’s possible to assert that in the browser and in the latest version of Node via X instanceof Request
, that is not an option in order Node versions and other environments where fetch is unavailable for one reason or another. Moreover, even when the entire ecosystem migrates to have fetch()
available everywhere, that doesn’t mean there will be no room for polyfills.
My reasoning comes from an open-source maintainer’s point of view. I maintain a library that consumes Fetch API primitives defined by the library user. Since the user may be relying on global fetch, or they may be relying on a fetch polyfill, it becomes challenging, if not virtually impossible, to find a common ground for such a variable input. This manifests in the fact that despite every fetch polyfill having the intention to implement the specification (some to a bigger extent than others) , all polyfills lack the validity (and identity) of their Request/Response/etc. polyfill classes behind internal symbols or class inheritance. This makes it impossible to provide, let’s say, a Request instance created by one polyfill to be consumed by another.
I do acknowledge that this is rather an edge case. But as a person who’s been working with this for half a decade now, I would very much appreciate a standardized way to denote Fetch API primitives so that, hopefully, polyfill implementations and other packages that manipulate, consume, or create such primitives could coexist and cooperate better.
Implementation example
This proposal advocates for the introduction of new internal Symbols that would clearly denote Fetch API primitives, such as Request, Response, and Headers instances. For example:
function isValidRequest(input) {
return Symbol.Request in input
}
I advocate for using Symbols vs other methods due to their exclusive and explicit nature. The primitives’ validity logic cannot truly be substituted by any other means at the moment, as none of those means would be reliable or explicit on the definition side.
// Relying on the string representation of the primitives
// is not a valid approach.
input.toString() // "[object Request]"
// Inheritance will only be true if the "input" and the
// "Request" were created from the same source. That's not
// the case if you're consuming an unknown polyfill,
// as the nature of "input" becomes dynamic but your, as the
// library author, must rely on a *single* Request class for
// this comparison.
input instanceof Request
// Checking specific properties and methods is brittle
// and leads to false positive matches quite easily.
'method' in input && 'url' in input
Since I’ve heard some share of criticism toward the standard Symbols in JavaScript, I may alternatively propose to introduce a single Symbol that would act as the denominator for the Fetch API primitives’ validity. For example:
input[Symbol.toFetchPrimitive] === 'Request'
Alternatively, any appropriate existing Symbol can be reused to achieve the same goal:
input[Symbol.species] === "FetchRequest'
Though, I do acknowledge that the current state of standard Symbols’ descriptiveness is an implicit indicator that it’s okay to have specific purpose-driven symbols. Thus, we have things like
Symbol.split
andSymbol.isConcatSpreadable
that are extremely specific.
Benefits
- If the implementation includes the aforementioned new standard Symbol(s), it explicitly marks a respective object as a valid Fetch API primitive. This creates a conscious contract that the implementation author follows, preventing false positive matches and ensuring specification compliance.