Skip to content

Commit 28a574e

Browse files
authored
Try assigning fetch to globalThis if global assignment fails (#25571)
In case it's a more modern yet rigid environment.
1 parent 09def59 commit 28a574e

File tree

8 files changed

+93
-80
lines changed

8 files changed

+93
-80
lines changed

.eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -277,5 +277,6 @@ module.exports = {
277277
trustedTypes: 'readonly',
278278
IS_REACT_ACT_ENVIRONMENT: 'readonly',
279279
AsyncLocalStorage: 'readonly',
280+
globalThis: 'readonly',
280281
},
281282
};

packages/react/src/ReactFetch.js

+85-79
Original file line numberDiff line numberDiff line change
@@ -42,92 +42,98 @@ function generateCacheKey(request: Request): string {
4242
if (enableCache && enableFetchInstrumentation) {
4343
if (typeof fetch === 'function') {
4444
const originalFetch = fetch;
45-
try {
46-
// eslint-disable-next-line no-native-reassign
47-
fetch = function fetch(
48-
resource: URL | RequestInfo,
49-
options?: RequestOptions,
45+
const cachedFetch = function fetch(
46+
resource: URL | RequestInfo,
47+
options?: RequestOptions,
48+
) {
49+
const dispatcher = ReactCurrentCache.current;
50+
if (!dispatcher) {
51+
// We're outside a cached scope.
52+
return originalFetch(resource, options);
53+
}
54+
if (
55+
options &&
56+
options.signal &&
57+
options.signal !== dispatcher.getCacheSignal()
5058
) {
51-
const dispatcher = ReactCurrentCache.current;
52-
if (!dispatcher) {
53-
// We're outside a cached scope.
54-
return originalFetch(resource, options);
55-
}
59+
// If we're passed a signal that is not ours, then we assume that
60+
// someone else controls the lifetime of this object and opts out of
61+
// caching. It's effectively the opt-out mechanism.
62+
// Ideally we should be able to check this on the Request but
63+
// it always gets initialized with its own signal so we don't
64+
// know if it's supposed to override - unless we also override the
65+
// Request constructor.
66+
return originalFetch(resource, options);
67+
}
68+
// Normalize the Request
69+
let url: string;
70+
let cacheKey: string;
71+
if (typeof resource === 'string' && !options) {
72+
// Fast path.
73+
cacheKey = simpleCacheKey;
74+
url = resource;
75+
} else {
76+
// Normalize the request.
77+
const request = new Request(resource, options);
5678
if (
57-
options &&
58-
options.signal &&
59-
options.signal !== dispatcher.getCacheSignal()
79+
(request.method !== 'GET' && request.method !== 'HEAD') ||
80+
// $FlowFixMe: keepalive is real
81+
request.keepalive
6082
) {
61-
// If we're passed a signal that is not ours, then we assume that
62-
// someone else controls the lifetime of this object and opts out of
63-
// caching. It's effectively the opt-out mechanism.
64-
// Ideally we should be able to check this on the Request but
65-
// it always gets initialized with its own signal so we don't
66-
// know if it's supposed to override - unless we also override the
67-
// Request constructor.
83+
// We currently don't dedupe requests that might have side-effects. Those
84+
// have to be explicitly cached. We assume that the request doesn't have a
85+
// body if it's GET or HEAD.
86+
// keepalive gets treated the same as if you passed a custom cache signal.
6887
return originalFetch(resource, options);
6988
}
70-
// Normalize the Request
71-
let url: string;
72-
let cacheKey: string;
73-
if (typeof resource === 'string' && !options) {
74-
// Fast path.
75-
cacheKey = simpleCacheKey;
76-
url = resource;
77-
} else {
78-
// Normalize the request.
79-
const request = new Request(resource, options);
80-
if (
81-
(request.method !== 'GET' && request.method !== 'HEAD') ||
82-
// $FlowFixMe: keepalive is real
83-
request.keepalive
84-
) {
85-
// We currently don't dedupe requests that might have side-effects. Those
86-
// have to be explicitly cached. We assume that the request doesn't have a
87-
// body if it's GET or HEAD.
88-
// keepalive gets treated the same as if you passed a custom cache signal.
89-
return originalFetch(resource, options);
89+
cacheKey = generateCacheKey(request);
90+
url = request.url;
91+
}
92+
const cache = dispatcher.getCacheForType(createFetchCache);
93+
const cacheEntries = cache.get(url);
94+
let match;
95+
if (cacheEntries === undefined) {
96+
// We pass the original arguments here in case normalizing the Request
97+
// doesn't include all the options in this environment.
98+
match = originalFetch(resource, options);
99+
cache.set(url, [cacheKey, match]);
100+
} else {
101+
// We use an array as the inner data structure since it's lighter and
102+
// we typically only expect to see one or two entries here.
103+
for (let i = 0, l = cacheEntries.length; i < l; i += 2) {
104+
const key = cacheEntries[i];
105+
const value = cacheEntries[i + 1];
106+
if (key === cacheKey) {
107+
match = value;
108+
// I would've preferred a labelled break but lint says no.
109+
return match.then(response => response.clone());
90110
}
91-
cacheKey = generateCacheKey(request);
92-
url = request.url;
93111
}
94-
const cache = dispatcher.getCacheForType(createFetchCache);
95-
const cacheEntries = cache.get(url);
96-
let match;
97-
if (cacheEntries === undefined) {
98-
// We pass the original arguments here in case normalizing the Request
99-
// doesn't include all the options in this environment.
100-
match = originalFetch(resource, options);
101-
cache.set(url, [cacheKey, match]);
102-
} else {
103-
// We use an array as the inner data structure since it's lighter and
104-
// we typically only expect to see one or two entries here.
105-
for (let i = 0, l = cacheEntries.length; i < l; i += 2) {
106-
const key = cacheEntries[i];
107-
const value = cacheEntries[i + 1];
108-
if (key === cacheKey) {
109-
match = value;
110-
// I would've preferred a labelled break but lint says no.
111-
return match.then(response => response.clone());
112-
}
113-
}
114-
match = originalFetch(resource, options);
115-
cacheEntries.push(cacheKey, match);
116-
}
117-
// We clone the response so that each time you call this you get a new read
118-
// of the body so that it can be read multiple times.
119-
return match.then(response => response.clone());
120-
};
121-
// We don't expect to see any extra properties on fetch but if there are any,
122-
// copy them over. Useful for extended fetch environments or mocks.
123-
Object.assign(fetch, originalFetch);
124-
} catch (error) {
125-
// Log even in production just to make sure this is seen if only prod is frozen.
126-
// eslint-disable-next-line react-internal/no-production-logging
127-
console.warn(
128-
'React was unable to patch the fetch() function in this environment. ' +
129-
'Suspensey APIs might not work correctly as a result.',
130-
);
112+
match = originalFetch(resource, options);
113+
cacheEntries.push(cacheKey, match);
114+
}
115+
// We clone the response so that each time you call this you get a new read
116+
// of the body so that it can be read multiple times.
117+
return match.then(response => response.clone());
118+
};
119+
// We don't expect to see any extra properties on fetch but if there are any,
120+
// copy them over. Useful for extended fetch environments or mocks.
121+
Object.assign(cachedFetch, originalFetch);
122+
try {
123+
// eslint-disable-next-line no-native-reassign
124+
fetch = cachedFetch;
125+
} catch (error1) {
126+
try {
127+
// In case assigning it globally fails, try globalThis instead just in case it exists.
128+
globalThis.fetch = cachedFetch;
129+
} catch (error2) {
130+
// Log even in production just to make sure this is seen if only prod is frozen.
131+
// eslint-disable-next-line react-internal/no-production-logging
132+
console.warn(
133+
'React was unable to patch the fetch() function in this environment. ' +
134+
'Suspensey APIs might not work correctly as a result.',
135+
);
136+
}
131137
}
132138
}
133139
}

scripts/flow/environment.js

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: any; /*?{
1818
inject: ?((stuff: Object) => void)
1919
};*/
2020

21+
declare var globalThis: Object;
22+
2123
declare var queueMicrotask: (fn: Function) => void;
2224
declare var reportError: (error: mixed) => void;
2325

scripts/rollup/validate/eslintrc.cjs2015.js

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module.exports = {
1616
WeakSet: 'readonly',
1717
Uint16Array: 'readonly',
1818
Reflect: 'readonly',
19+
globalThis: 'readonly',
1920
// Vendor specific
2021
MSApp: 'readonly',
2122
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',

scripts/rollup/validate/eslintrc.esm.js

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = {
1515
WeakSet: 'readonly',
1616
Uint16Array: 'readonly',
1717
Reflect: 'readonly',
18+
globalThis: 'readonly',
1819
// Vendor specific
1920
MSApp: 'readonly',
2021
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',

scripts/rollup/validate/eslintrc.fb.js

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = {
1515
WeakSet: 'readonly',
1616
Uint16Array: 'readonly',
1717
Reflect: 'readonly',
18+
globalThis: 'readonly',
1819
// Vendor specific
1920
MSApp: 'readonly',
2021
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',

scripts/rollup/validate/eslintrc.rn.js

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module.exports = {
1414
WeakMap: 'readonly',
1515
WeakSet: 'readonly',
1616
Reflect: 'readonly',
17+
globalThis: 'readonly',
1718
// Vendor specific
1819
MSApp: 'readonly',
1920
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',

scripts/rollup/validate/eslintrc.umd.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module.exports = {
1414
WeakSet: 'readonly',
1515
Uint16Array: 'readonly',
1616
Reflect: 'readonly',
17+
globalThis: 'readonly',
1718
// Vendor specific
1819
MSApp: 'readonly',
1920
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',
@@ -24,7 +25,6 @@ module.exports = {
2425
module: 'readonly',
2526
define: 'readonly',
2627
require: 'readonly',
27-
globalThis: 'readonly',
2828
global: 'readonly',
2929
// Internet Explorer
3030
setImmediate: 'readonly',

0 commit comments

Comments
 (0)