Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Try assigning fetch to globalThis if global assignment fails #25571

Merged
merged 1 commit into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,5 +277,6 @@ module.exports = {
trustedTypes: 'readonly',
IS_REACT_ACT_ENVIRONMENT: 'readonly',
AsyncLocalStorage: 'readonly',
globalThis: 'readonly',
},
};
164 changes: 85 additions & 79 deletions packages/react/src/ReactFetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,92 +42,98 @@ function generateCacheKey(request: Request): string {
if (enableCache && enableFetchInstrumentation) {
if (typeof fetch === 'function') {
const originalFetch = fetch;
try {
// eslint-disable-next-line no-native-reassign
fetch = function fetch(
resource: URL | RequestInfo,
options?: RequestOptions,
const cachedFetch = function fetch(
resource: URL | RequestInfo,
options?: RequestOptions,
) {
const dispatcher = ReactCurrentCache.current;
if (!dispatcher) {
// We're outside a cached scope.
return originalFetch(resource, options);
}
if (
options &&
options.signal &&
options.signal !== dispatcher.getCacheSignal()
) {
const dispatcher = ReactCurrentCache.current;
if (!dispatcher) {
// We're outside a cached scope.
return originalFetch(resource, options);
}
// If we're passed a signal that is not ours, then we assume that
// someone else controls the lifetime of this object and opts out of
// caching. It's effectively the opt-out mechanism.
// Ideally we should be able to check this on the Request but
// it always gets initialized with its own signal so we don't
// know if it's supposed to override - unless we also override the
// Request constructor.
return originalFetch(resource, options);
}
// Normalize the Request
let url: string;
let cacheKey: string;
if (typeof resource === 'string' && !options) {
// Fast path.
cacheKey = simpleCacheKey;
url = resource;
} else {
// Normalize the request.
const request = new Request(resource, options);
if (
options &&
options.signal &&
options.signal !== dispatcher.getCacheSignal()
(request.method !== 'GET' && request.method !== 'HEAD') ||
// $FlowFixMe: keepalive is real
request.keepalive
) {
// If we're passed a signal that is not ours, then we assume that
// someone else controls the lifetime of this object and opts out of
// caching. It's effectively the opt-out mechanism.
// Ideally we should be able to check this on the Request but
// it always gets initialized with its own signal so we don't
// know if it's supposed to override - unless we also override the
// Request constructor.
// We currently don't dedupe requests that might have side-effects. Those
// have to be explicitly cached. We assume that the request doesn't have a
// body if it's GET or HEAD.
// keepalive gets treated the same as if you passed a custom cache signal.
return originalFetch(resource, options);
}
// Normalize the Request
let url: string;
let cacheKey: string;
if (typeof resource === 'string' && !options) {
// Fast path.
cacheKey = simpleCacheKey;
url = resource;
} else {
// Normalize the request.
const request = new Request(resource, options);
if (
(request.method !== 'GET' && request.method !== 'HEAD') ||
// $FlowFixMe: keepalive is real
request.keepalive
) {
// We currently don't dedupe requests that might have side-effects. Those
// have to be explicitly cached. We assume that the request doesn't have a
// body if it's GET or HEAD.
// keepalive gets treated the same as if you passed a custom cache signal.
return originalFetch(resource, options);
cacheKey = generateCacheKey(request);
url = request.url;
}
const cache = dispatcher.getCacheForType(createFetchCache);
const cacheEntries = cache.get(url);
let match;
if (cacheEntries === undefined) {
// We pass the original arguments here in case normalizing the Request
// doesn't include all the options in this environment.
match = originalFetch(resource, options);
cache.set(url, [cacheKey, match]);
} else {
// We use an array as the inner data structure since it's lighter and
// we typically only expect to see one or two entries here.
for (let i = 0, l = cacheEntries.length; i < l; i += 2) {
const key = cacheEntries[i];
const value = cacheEntries[i + 1];
if (key === cacheKey) {
match = value;
// I would've preferred a labelled break but lint says no.
return match.then(response => response.clone());
}
cacheKey = generateCacheKey(request);
url = request.url;
}
const cache = dispatcher.getCacheForType(createFetchCache);
const cacheEntries = cache.get(url);
let match;
if (cacheEntries === undefined) {
// We pass the original arguments here in case normalizing the Request
// doesn't include all the options in this environment.
match = originalFetch(resource, options);
cache.set(url, [cacheKey, match]);
} else {
// We use an array as the inner data structure since it's lighter and
// we typically only expect to see one or two entries here.
for (let i = 0, l = cacheEntries.length; i < l; i += 2) {
const key = cacheEntries[i];
const value = cacheEntries[i + 1];
if (key === cacheKey) {
match = value;
// I would've preferred a labelled break but lint says no.
return match.then(response => response.clone());
}
}
match = originalFetch(resource, options);
cacheEntries.push(cacheKey, match);
}
// We clone the response so that each time you call this you get a new read
// of the body so that it can be read multiple times.
return match.then(response => response.clone());
};
// We don't expect to see any extra properties on fetch but if there are any,
// copy them over. Useful for extended fetch environments or mocks.
Object.assign(fetch, originalFetch);
} catch (error) {
// Log even in production just to make sure this is seen if only prod is frozen.
// eslint-disable-next-line react-internal/no-production-logging
console.warn(
'React was unable to patch the fetch() function in this environment. ' +
'Suspensey APIs might not work correctly as a result.',
);
match = originalFetch(resource, options);
cacheEntries.push(cacheKey, match);
}
// We clone the response so that each time you call this you get a new read
// of the body so that it can be read multiple times.
return match.then(response => response.clone());
};
// We don't expect to see any extra properties on fetch but if there are any,
// copy them over. Useful for extended fetch environments or mocks.
Object.assign(cachedFetch, originalFetch);
try {
// eslint-disable-next-line no-native-reassign
fetch = cachedFetch;
} catch (error1) {
try {
// In case assigning it globally fails, try globalThis instead just in case it exists.
globalThis.fetch = cachedFetch;
} catch (error2) {
// Log even in production just to make sure this is seen if only prod is frozen.
// eslint-disable-next-line react-internal/no-production-logging
console.warn(
'React was unable to patch the fetch() function in this environment. ' +
'Suspensey APIs might not work correctly as a result.',
);
}
}
}
}
2 changes: 2 additions & 0 deletions scripts/flow/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: any; /*?{
inject: ?((stuff: Object) => void)
};*/

declare var globalThis: Object;

Comment on lines +21 to +22
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this like typing it as any when it is quite unsafe to do so? why not just suppress the flow warning where you attempt to reference it if you don't want to existence check it?

declare var queueMicrotask: (fn: Function) => void;
declare var reportError: (error: mixed) => void;

Expand Down
1 change: 1 addition & 0 deletions scripts/rollup/validate/eslintrc.cjs2015.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module.exports = {
WeakSet: 'readonly',
Uint16Array: 'readonly',
Reflect: 'readonly',
globalThis: 'readonly',
// Vendor specific
MSApp: 'readonly',
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',
Expand Down
1 change: 1 addition & 0 deletions scripts/rollup/validate/eslintrc.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = {
WeakSet: 'readonly',
Uint16Array: 'readonly',
Reflect: 'readonly',
globalThis: 'readonly',
// Vendor specific
MSApp: 'readonly',
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',
Expand Down
1 change: 1 addition & 0 deletions scripts/rollup/validate/eslintrc.fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = {
WeakSet: 'readonly',
Uint16Array: 'readonly',
Reflect: 'readonly',
globalThis: 'readonly',
// Vendor specific
MSApp: 'readonly',
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',
Expand Down
1 change: 1 addition & 0 deletions scripts/rollup/validate/eslintrc.rn.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports = {
WeakMap: 'readonly',
WeakSet: 'readonly',
Reflect: 'readonly',
globalThis: 'readonly',
// Vendor specific
MSApp: 'readonly',
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',
Expand Down
2 changes: 1 addition & 1 deletion scripts/rollup/validate/eslintrc.umd.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports = {
WeakSet: 'readonly',
Uint16Array: 'readonly',
Reflect: 'readonly',
globalThis: 'readonly',
// Vendor specific
MSApp: 'readonly',
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',
Expand All @@ -24,7 +25,6 @@ module.exports = {
module: 'readonly',
define: 'readonly',
require: 'readonly',
globalThis: 'readonly',
global: 'readonly',
// Internet Explorer
setImmediate: 'readonly',
Expand Down