Try assigning fetch to globalThis if global assignment fails (#25571)
In case it's a more modern yet rigid environment.
Sebastian Markbåge committed
Oct 27, 2022 at 02:43 UTC
28a574ea8f5d0efa95f861abca26a67af47dd0c0
8 files changed
+93
-80
.eslintrc.js
+1
@@ -277,5 +277,6 @@ module.exports = {
277
trustedTypes: 'readonly',
278
IS_REACT_ACT_ENVIRONMENT: 'readonly',
279
AsyncLocalStorage: 'readonly',
280
+ globalThis: 'readonly',
281
},
282
};
packages/react/src/ReactFetch.js
+85
-79
@@ -42,92 +42,98 @@ function generateCacheKey(request: Request): string {
42
if (enableCache && enableFetchInstrumentation) {
43
if (typeof fetch === 'function') {
44
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()
58
) {
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);
78
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
82
) {
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.
87
return originalFetch(resource, options);
88
}
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());
110
}
91
- cacheKey = generateCacheKey(request);
92
- url = request.url;
111
}
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
+ }
137
}
138
}
139
}
scripts/flow/environment.js
+2
@@ -18,6 +18,8 @@ declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: any; /*?{
18
inject: ?((stuff: Object) => void)
19
};*/
20
21
+declare var globalThis: Object;
22
+
23
declare var queueMicrotask: (fn: Function) => void;
24
declare var reportError: (error: mixed) => void;
25
scripts/rollup/validate/eslintrc.cjs2015.js
+1
@@ -16,6 +16,7 @@ module.exports = {
16
WeakSet: 'readonly',
17
Uint16Array: 'readonly',
18
Reflect: 'readonly',
19
+ globalThis: 'readonly',
20
// Vendor specific
21
MSApp: 'readonly',
22
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',
scripts/rollup/validate/eslintrc.esm.js
+1
@@ -15,6 +15,7 @@ module.exports = {
15
WeakSet: 'readonly',
16
Uint16Array: 'readonly',
17
Reflect: 'readonly',
18
+ globalThis: 'readonly',
19
// Vendor specific
20
MSApp: 'readonly',
21
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',
scripts/rollup/validate/eslintrc.fb.js
+1
@@ -15,6 +15,7 @@ module.exports = {
15
WeakSet: 'readonly',
16
Uint16Array: 'readonly',
17
Reflect: 'readonly',
18
+ globalThis: 'readonly',
19
// Vendor specific
20
MSApp: 'readonly',
21
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',
scripts/rollup/validate/eslintrc.rn.js
+1
@@ -14,6 +14,7 @@ module.exports = {
14
WeakMap: 'readonly',
15
WeakSet: 'readonly',
16
Reflect: 'readonly',
17
+ globalThis: 'readonly',
18
// Vendor specific
19
MSApp: 'readonly',
20
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',
scripts/rollup/validate/eslintrc.umd.js
+1
-1
@@ -14,6 +14,7 @@ module.exports = {
14
WeakSet: 'readonly',
15
Uint16Array: 'readonly',
16
Reflect: 'readonly',
17
+ globalThis: 'readonly',
18
// Vendor specific
19
MSApp: 'readonly',
20
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',
@@ -24,7 +25,6 @@ module.exports = {
25
module: 'readonly',
26
define: 'readonly',
27
require: 'readonly',
27
- globalThis: 'readonly',
28
global: 'readonly',
29
// Internet Explorer
30
setImmediate: 'readonly',