@samitouri / QOS-React-2 / commits / 28a574ea8f

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',