mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Try assigning fetch to globalThis if global assignment fails (#25571)
In case it's a more modern yet rigid environment.
This commit is contained in:
1 parent
09def5990b
commit
28a574ea8f
8 files changed
+98
-85
No files matched your search
@@ -277,5 +277,6 @@ module.exports = {
|
||||
trustedTypes: 'readonly',
|
||||
IS_REACT_ACT_ENVIRONMENT: 'readonly',
|
||||
AsyncLocalStorage: 'readonly',
|
||||
globalThis: 'readonly',
|
||||
},
|
||||
};
|
||||
@@ -42,92 +42,98 @@ function generateCacheKey(request: Request): string {
|
||||
if (enableCache && enableFetchInstrumentation) {
|
||||
if (typeof fetch === 'function') {
|
||||
const originalFetch = fetch;
|
||||
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()
|
||||
) {
|
||||
// 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 (
|
||||
(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());
|
||||
}
|
||||
}
|
||||
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 = 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()
|
||||
) {
|
||||
// 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 (
|
||||
(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());
|
||||
}
|
||||
}
|
||||
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.',
|
||||
);
|
||||
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.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: any; /*?{
|
||||
inject: ?((stuff: Object) => void)
|
||||
};*/
|
||||
|
||||
declare var globalThis: Object;
|
||||
|
||||
declare var queueMicrotask: (fn: Function) => void;
|
||||
declare var reportError: (error: mixed) => void;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ module.exports = {
|
||||
WeakSet: 'readonly',
|
||||
Uint16Array: 'readonly',
|
||||
Reflect: 'readonly',
|
||||
globalThis: 'readonly',
|
||||
// Vendor specific
|
||||
MSApp: 'readonly',
|
||||
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',
|
||||
|
||||
@@ -15,6 +15,7 @@ module.exports = {
|
||||
WeakSet: 'readonly',
|
||||
Uint16Array: 'readonly',
|
||||
Reflect: 'readonly',
|
||||
globalThis: 'readonly',
|
||||
// Vendor specific
|
||||
MSApp: 'readonly',
|
||||
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',
|
||||
|
||||
@@ -15,6 +15,7 @@ module.exports = {
|
||||
WeakSet: 'readonly',
|
||||
Uint16Array: 'readonly',
|
||||
Reflect: 'readonly',
|
||||
globalThis: 'readonly',
|
||||
// Vendor specific
|
||||
MSApp: 'readonly',
|
||||
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',
|
||||
|
||||
@@ -14,6 +14,7 @@ module.exports = {
|
||||
WeakMap: 'readonly',
|
||||
WeakSet: 'readonly',
|
||||
Reflect: 'readonly',
|
||||
globalThis: 'readonly',
|
||||
// Vendor specific
|
||||
MSApp: 'readonly',
|
||||
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',
|
||||
|
||||
@@ -14,6 +14,7 @@ module.exports = {
|
||||
WeakSet: 'readonly',
|
||||
Uint16Array: 'readonly',
|
||||
Reflect: 'readonly',
|
||||
globalThis: 'readonly',
|
||||
// Vendor specific
|
||||
MSApp: 'readonly',
|
||||
__REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly',
|
||||
@@ -24,7 +25,6 @@ module.exports = {
|
||||
module: 'readonly',
|
||||
define: 'readonly',
|
||||
require: 'readonly',
|
||||
globalThis: 'readonly',
|
||||
global: 'readonly',
|
||||
// Internet Explorer
|
||||
setImmediate: 'readonly',
|
||||
|
||||
Reference in new issue
Block a user