# Introduce standard Symbols for Fetch API primitives

**URL:** <https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597>\
**Category:** 💡 Ideas\
**Created:** [January 24, 2023, 3:55pm UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597 "2023-01-24T15:55:33Z")\
**Posts on this page:** 19\
**Page:** 1

<div class="post-metadata">

**Author:** ![kettanaito](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/kettanaito/32/1623_2.png) [@kettanaito](https://es.discourse.group/u/kettanaito)\
**Post date:** [January 24, 2023, 3:55pm UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/1 "2023-01-24T15:55:33Z")

</div>

## 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:

```javascript
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.

```javascript
// 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:

```javascript
input[Symbol.toFetchPrimitive] === 'Request'

```

Alternatively, any appropriate existing Symbol can be reused to achieve the same goal:

```javascript
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` and `Symbol.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.

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [January 24, 2023, 6:28pm UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/2 "2023-01-24T18:28:47Z")

</div>

The fetch API is not part of JavaScript; it's part of HTML, and it wouldn't be appropriate to put Symbols for it on Symbol.

You may want to discuss his on a WHATWG repo.

---

<div class="post-metadata">

**Author:** ![kettanaito](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/kettanaito/32/1623_2.png) [@kettanaito](https://es.discourse.group/u/kettanaito)\
**Post date:** [January 24, 2023, 6:33pm UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/3 "2023-01-24T18:33:52Z")

</div>

Thanks for a swift response on this, @ljharb! Makes sense, I will open a new proposal with WHATWG.

---

<div class="post-metadata">

**Author:** ![tabatkins](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/tabatkins/32/170_2.png) [@tabatkins](https://es.discourse.group/u/tabatkins)\
**Post date:** [January 24, 2023, 7:04pm UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/4 "2023-01-24T19:04:49Z")

</div>

More importantly, you yourself say:

> 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

Older versions of Node won't have the new symbols, and newer versions don't need the new symbols. I don't understand what value the new symbols would bring, then.

---

<div class="post-metadata">

**Author:** ![kettanaito](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/kettanaito/32/1623_2.png) [@kettanaito](https://es.discourse.group/u/kettanaito)\
**Post date:** [January 24, 2023, 8:12pm UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/5 "2023-01-24T20:12:24Z")

</div>

They would serve as an explicit way to say "hey, this object is a valid Request/Responst/etc". I'm aiming at making the work with polyfills a bit better. I acknowledge this will become less and less of an issue as time goes on and the global fetch adoption increases with the modern versions of Node.

I wish I raised this sooner. The fetch support in Node is great but it will take time until the entire ecosystem gets there.

But it makes sense what you say, I didn't take into account _how_ the older versions of Node would get this symbol.

---

<div class="post-metadata">

**Author:** ![nickmccurdy](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/nickmccurdy/32/2327_2.png) [@nickmccurdy](https://es.discourse.group/u/nickmccurdy)\
**Post date:** [January 24, 2023, 11:35pm UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/6 "2023-01-24T23:35:47Z")

</div>

As long as they support ES6, they can use the Symbol, regardless of whether it's exposed globally on the Symbol object

---

<div class="post-metadata">

**Author:** ![tabatkins](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/tabatkins/32/170_2.png) [@tabatkins](https://es.discourse.group/u/tabatkins)\
**Post date:** [January 25, 2023, 1:15am UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/7 "2023-01-25T01:15:19Z")

</div>

My point is that older versions of Node won't expose the new symbol, because they're old and thus predate the new symbol. You can't get around something not being implemented by specifying something new to implement.

---

<div class="post-metadata">

**Author:** ![nickmccurdy](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/nickmccurdy/32/2327_2.png) [@nickmccurdy](https://es.discourse.group/u/nickmccurdy)\
**Post date:** [January 25, 2023, 2:17am UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/8 "2023-01-25T02:17:00Z")

</div>

It doesn't matter if Node exposes the Symbol as long as it supports Symbols. Node has fully supported them since version 0.12, and considering support for those versions ended 4 years ago, I doubt this would be an issue for anyone.

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [January 25, 2023, 9:04pm UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/9 "2023-01-25T21:04:28Z")

</div>

I'm also fairly confused at how this would handle anything.

In the case of an outdated node enviornment that has a single, up-to-date fetch polyfill on it, then both "`Symbol.Request in input`" and "`input instanceof Request`" would work, since there's a single polyfill installed on the system, and all request objects would be an instance of that polyfill.

In the case of two different polyfills on the same system - for that to be possible, these polyfills must not actually be installed on the global object, otherwise there would be a conflict and only one polyfill would win. If these polyfills are not putting `fetch` onto the global object, I must assume that they also wouldn't attaching a `Request` symbol onto the `Symbol` class for the same reason (and, instead, you'd have to import this new symbol from the polyfill). If this happens, then there would be two different `Request` objects, and there would be two different symbols, which means neither `Symbol.Request in input` nor `input instanceof Request` would work.

---

<div class="post-metadata">

**Author:** ![kettanaito](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/kettanaito/32/1623_2.png) [@kettanaito](https://es.discourse.group/u/kettanaito)\
**Post date:** [January 25, 2023, 11:36pm UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/10 "2023-01-25T23:36:23Z")

</div>

True. That's why I mentioned that this is rather an edge case.

My motivation here is to be able to determine Fetch primitives' validity in an ambiguous context. Imagine writing a package that operates on Fetch API requests. So it may look something like this:

```plaintext
function isValidRequest(request) {}

```

Now, since it's the consume who's supplying the `request`, it can be either a global fetch or a fetch polyfill. Despite those two implementing the same specification, they won't be runtime-compatible. Fetch polyfills lock the identity of fetch primitives under internal symbols (I tend to believe for the lack of the better means), which means that even if you provide a polyfill with a valid Request implementation (e.g. from another polyfill) it will reject it.

I find that to be against the purpose of having a specification. And this ambiguous context is possible if your library _also_ need to construct a Request or Headers. It cannot possible know what polyfill the consumer would use, if any. So the library installs a fixed, known polyfill. Suddenly, provided Request instances are not compatible with that internal library's polyfill.

That's where having a standardized Symbol seems like an idea worth exploring. I hope I brought some more light over the context of this proposal.

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [January 26, 2023, 12:25am UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/11 "2023-01-26T00:25:57Z")

</div>

I think I understand the problem you're facing, I'm just not understanding the proposed solution. Perhaps a concrete example would help illustrate the issue?

```javascript
// fetch-polyfill-a.js

export function fetch() { ... }

export class Request { ... }

export const RequestSymbol = Symbol('Request');

export function installOnGlobal() {
  if (!globalThis.fetch) globalThis.fetch = fetch;
  if (!globalThis.Request) globalThis.Request = Request;
  if (!globalThis.Symbol.Request) globalThis.Symbol.Request = RequestSymbol;
}

// fetch-polyfill-b.js
// (it's exactly the same as fetch-polyfill-a.js, so don't bother looking too closely)

export function fetch() { ... }

export class Request { ... }

export const RequestSymbol = Symbol('Request');

export function installOnGlobal() {
  if (!globalThis.fetch) globalThis.fetch = fetch;
  if (!globalThis.Request) globalThis.Request = Request;
  if (!globalThis.Symbol.Request) globalThis.Symbol.Request = RequestSymbol;
}

// yourLibrary.js

export function doFancyStuff(requestObj) {
  if (!(Symbol.Request in requestObj)) {
    throw new Error('You must supply a valid request object');
  }
  ...
}

// main.js

import * as fetchPolyfillA from './fetch-polyfill-a.js';
import * as fetchPolyfillB from './fetch-polyfill-b.js';
import { doFancyStuff } from './yourLibrary.js';

// We only install one of these on the global object, otherwise we get conflicts.
fetchPolyfillA.installOnGlobal();

doFancyStuff(new fetchPolyfillB.Request(...)); // Error: "You must supply a valid request object"

```

Here, I have an example of two polyfills, a library like yours, and a main script that's using all of them. We can see that, even if we had a new special symbol, there still would be the same issues because there's two versions of that symbol.

I think that currently, the best solution a library author can do in a scenario like this, is to, by default, try to use whatever `Request` object is found on globalThis with instanceof checks. If a user wishes to use a polyfilled Request object that's not found on globalThis, they'll need to register this polyfilled Request class with the library first. This is commonly how libraries handle the various promise APIs that are out there - they, by default, use the built-in promise API, but if you, instead, wish to use bluebird promises or something, you have to register the bluebird promises with the library, and then it'll start using those instead (it's a different problem being solved, but the same solution could be used here)

---

<div class="post-metadata">

**Author:** ![bergus](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/bergus/32/152_2.png) [@bergus](https://es.discourse.group/u/bergus)\
**Post date:** [January 26, 2023, 2:40am UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/12 "2023-01-26T02:40:31Z")

</div>

Hi,  
I don't think introducing new well-known intrinsic symbols would pose a barrier to polyfills. However, I don't quite understand the problem either

> [@kettanaito](#):
>
> how can I be sure that a given object is a valid Request per Fetch API specification?

Why would you care whether it's "valid"? All that matters is that you can pass it to `fetch()`, no? And for that, duck typing is a better solution than putting special symbols on those objects (which can be faked as well). As you say, the goal is that you can manipulate, consume and create `Request` objects. A symbol doesn't really help with that. What helps is the standard property names that are adhered by all implementations - an indeed, the following should work just fine, since `fetch` and the `Request` constructor accept other requests and **request-like objects** :

```javascript
const a = new packageA.Request({…});
const b = new packageB.Request(a, {…});
const r = await packageC.fetch(b);

```

---

<div class="post-metadata">

**Author:** ![kettanaito](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/kettanaito/32/1623_2.png) [@kettanaito](https://es.discourse.group/u/kettanaito)\
**Post date:** [January 26, 2023, 6:02pm UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/13 "2023-01-26T18:02:30Z")

</div>

Okay, let me provide you with a better example then.

Criteria: write a function that accepts a `Request` instance and sets its `headers` property to a `Headers` instance if it's not set already. The function itself _doesn't create the Request instance_.

```plaintext
function act(request) {
  if (typeof request.headers === 'undefined') {
    request.headers = new Headers({
      'X-Example': 'yes'
    })
  }
}

```

The challenge here is that both the Request and the Headers instance must come _from the same source_: either the same polyfill or the same global references.

Imagine a polyfill scenario: a consumer of the `act` function is using a polyfill, so the passed Request instance is an object created by that polyfill. The `act` function cannot predict what polyfill is going to be used. If it's going to construct a global `Headers` object and set it on `request.headers`, the polyfill will _not treat that object as a Headers instance_ because that only yields true if a given object has a specific internal symbol of the polyfill (i.e. any polyfill only treats fetch primitives as valid if they have been created by the same polyfill, regardless if they actually fulfill the Fetch API specification or not).

Once again, this is an edge case. And yet, it's a totally reasonable functionality to have in libraries that deal with request/response transformations in ambiguous contexts.

I don't believe you can correctly change any passed request instance unless the library and the consumer use the same polyfill (impossible to know, the library doesn't and mustn't enforce any particular polyfill) or use the same global fetch (impossible to achieve when promising Node \< 17 support).

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [January 26, 2023, 6:22pm UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/14 "2023-01-26T18:22:47Z")

</div>

Symbols won't help you if a polyfill doesn't choose to use one.

In this case, it's the polyfill's responsibility - not yours - to ensure that its types and identities match that of the global, when it exists, as much as possible. If a `Request` exists in the env, then the polyfill needs to use it, otherwise, it's not a proper polyfill and your function _shouldn't_ work with it.

---

<div class="post-metadata">

**Author:** ![kettanaito](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/kettanaito/32/1623_2.png) [@kettanaito](https://es.discourse.group/u/kettanaito)\
**Post date:** [January 26, 2023, 7:39pm UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/15 "2023-01-26T19:39:50Z")

</div>

Of course. That's why if there was a Symbol I could encourage polyfill maintainers to adopt it.

Not a single commonly used Fetch polyfill concerns with validating its primitives against any globals. That's precisely my complain: there's no standardized way to say "hey, this object is a valid Fetch API Request object" because there's no definition of what a valid Request object is. So polyfills assume that the only valid object is the object that _they_ create. That is not the case if you work with complex tooling.

It's also too optimistic to assume that polyfilled primitives will be set to globals. That's up to the consumer of the polyfill and that's a good thing. You can import polyfill classes and then, if you want, set them globally.

Given the sheer explanation of the problematics is too challenging, perhaps it's worth waiting a few months before older versions of Node will get officially discontinued and then the global fetch will be a common thing.

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [January 26, 2023, 8:02pm UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/16 "2023-01-26T20:02:49Z")

</div>

Then I'd suggest using those advocacy powers to encourage the polyfills to match the globals whenever possible :-)

I totally agree that polyfills shouldn't automatically install themselves on the global, but they can still do `class Request extends globalThis.Request` when it's available.

---

<div class="post-metadata">

**Author:** ![kettanaito](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/kettanaito/32/1623_2.png) [@kettanaito](https://es.discourse.group/u/kettanaito)\
**Post date:** [January 26, 2023, 9:01pm UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/17 "2023-01-26T21:01:12Z")

</div>

Isn't the purpose of a polyfill is to provide the otherwise missing functionality?  
If the global exists in polyfill's context in order to extend it, then why use a polyfill in the first place?

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [January 26, 2023, 9:15pm UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/18 "2023-01-26T21:15:35Z")

</div>

a) the global one might be broken (they often are, in subtle ways)  
b) the exact use case you mentioned - wanting to support envs both with and without the builtin

---

<div class="post-metadata">

**Author:** ![kettanaito](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/kettanaito/32/1623_2.png) [@kettanaito](https://es.discourse.group/u/kettanaito)\
**Post date:** [January 26, 2023, 9:20pm UTC](https://es.discourse.group/t/introduce-standard-symbols-for-fetch-api-primitives/1597/19 "2023-01-26T21:20:24Z")

</div>

The B option is precisely what I went with: using the global primitives if present, otherwise falling back to a fixed polyfill used internally by the library (which is causing troubles due to incompatibility with other poltyfills as I've mentioned).

Appreciate the discussion around this!
