Skip to content

Commit e4de611

Browse files
committed
Use AsyncLocalStorage to extend the scope of the cache to micro tasks
1 parent db586ec commit e4de611

20 files changed

+98
-20
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,5 +276,6 @@ module.exports = {
276276
gate: 'readonly',
277277
trustedTypes: 'readonly',
278278
IS_REACT_ACT_ENVIRONMENT: 'readonly',
279+
AsyncLocalStorage: 'readonly',
279280
},
280281
};

packages/react-dom-bindings/src/server/ReactDOMLegacyServerStreamConfig.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ export function scheduleWork(callback: () => void) {
2121

2222
export function flushBuffered(destination: Destination) {}
2323

24+
export const supportsRequestStorage = false;
25+
export const requestStorage: AsyncLocalStorage<any> = (null: any);
26+
2427
export function beginWriting(destination: Destination) {}
2528

2629
export function writeChunk(

packages/react-server-dom-relay/src/ReactFlightDOMRelayServerHostConfig.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@ export function scheduleWork(callback: () => void) {
191191

192192
export function flushBuffered(destination: Destination) {}
193193

194+
export const supportsRequestStorage = false;
195+
export const requestStorage: AsyncLocalStorage<
196+
Map<Function, mixed>,
197+
> = (null: any);
198+
194199
export function beginWriting(destination: Destination) {}
195200

196201
export function writeChunk(destination: Destination, chunk: Chunk): void {

packages/react-server-dom-relay/src/ReactServerStreamConfigFB.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ export function scheduleWork(callback: () => void) {
2323

2424
export function flushBuffered(destination: Destination) {}
2525

26+
export const supportsRequestStorage = false;
27+
export const requestStorage: AsyncLocalStorage<
28+
Map<Function, mixed>,
29+
> = (null: any);
30+
2631
export function beginWriting(destination: Destination) {}
2732

2833
export function writeChunk(

packages/react-server-native-relay/src/ReactFlightNativeRelayServerHostConfig.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,11 @@ export function scheduleWork(callback: () => void) {
186186

187187
export function flushBuffered(destination: Destination) {}
188188

189+
export const supportsRequestStorage = false;
190+
export const requestStorage: AsyncLocalStorage<
191+
Map<Function, mixed>,
192+
> = (null: any);
193+
189194
export function beginWriting(destination: Destination) {}
190195

191196
export function writeChunk(destination: Destination, chunk: Chunk): void {

packages/react-server/src/ReactFlightCache.js

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,30 @@
99

1010
import type {CacheDispatcher} from 'react-reconciler/src/ReactInternalTypes';
1111

12+
import {
13+
supportsRequestStorage,
14+
requestStorage,
15+
} from './ReactFlightServerConfig';
16+
1217
function createSignal(): AbortSignal {
1318
return new AbortController().signal;
1419
}
1520

21+
function resolveCache(): Map<Function, mixed> {
22+
if (currentCache) return currentCache;
23+
if (supportsRequestStorage) {
24+
const cache = requestStorage.getStore();
25+
if (cache) return cache;
26+
}
27+
// Since we override the dispatcher all the time, we're effectively always
28+
// active and so to support cache() and fetch() outside of render, we yield
29+
// an empty Map.
30+
return new Map();
31+
}
32+
1633
export const DefaultCacheDispatcher: CacheDispatcher = {
1734
getCacheSignal(): AbortSignal {
18-
if (!currentCache) {
19-
throw new Error('Reading the cache is only supported while rendering.');
20-
}
21-
let entry: AbortSignal | void = (currentCache.get(createSignal): any);
35+
let entry: AbortSignal | void = (resolveCache().get(createSignal): any);
2236
if (entry === undefined) {
2337
entry = createSignal();
2438
// $FlowFixMe[incompatible-use] found when upgrading Flow
@@ -27,11 +41,7 @@ export const DefaultCacheDispatcher: CacheDispatcher = {
2741
return entry;
2842
},
2943
getCacheForType<T>(resourceType: () => T): T {
30-
if (!currentCache) {
31-
throw new Error('Reading the cache is only supported while rendering.');
32-
}
33-
34-
let entry: T | void = (currentCache.get(resourceType): any);
44+
let entry: T | void = (resolveCache().get(resourceType): any);
3545
if (entry === undefined) {
3646
entry = resourceType();
3747
// TODO: Warn if undefined?

packages/react-server/src/ReactFlightServer.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1263,7 +1263,11 @@ function flushCompletedChunks(
12631263
}
12641264

12651265
export function startWork(request: Request): void {
1266-
scheduleWork(() => performWork(request));
1266+
if (supportsRequestStorage) {
1267+
scheduleWork(() => requestStorage.run(request.cache, performWork, request));
1268+
} else {
1269+
scheduleWork(() => performWork(request));
1270+
}
12671271
}
12681272

12691273
export function startFlowing(request: Request, destination: Destination): void {

packages/react-server/src/ReactServerStreamConfigBrowser.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ export function flushBuffered(destination: Destination) {
2424
// For now we support AsyncLocalStorage as a global for the "browser" builds
2525
// TODO: Move this to some special WinterCG build.
2626
export const supportsRequestStorage = typeof AsyncLocalStorage === 'function';
27-
export const requestStorage = new AsyncLocalStorage();
27+
export const requestStorage: AsyncLocalStorage<
28+
Map<Function, mixed>,
29+
> = supportsRequestStorage ? new AsyncLocalStorage() : (null: any);
2830

2931
const VIEW_SIZE = 512;
3032
let currentView = null;

packages/react-server/src/ReactServerStreamConfigNode.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ export function flushBuffered(destination: Destination) {
3535
}
3636

3737
export const supportsRequestStorage = true;
38-
export const requestStorage = new AsyncLocalStorage();
38+
export const requestStorage: AsyncLocalStorage<
39+
Map<Function, mixed>,
40+
> = new AsyncLocalStorage();
3941

4042
const VIEW_SIZE = 2048;
4143
let currentView = null;

packages/react-server/src/forks/ReactServerStreamConfig.custom.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export const writeChunk = $$$hostConfig.writeChunk;
3535
export const writeChunkAndReturn = $$$hostConfig.writeChunkAndReturn;
3636
export const completeWriting = $$$hostConfig.completeWriting;
3737
export const flushBuffered = $$$hostConfig.flushBuffered;
38+
export const supportsRequestStorage = $$$hostConfig.supportsRequestStorage;
39+
export const requestStorage = $$$hostConfig.requestStorage;
3840
export const close = $$$hostConfig.close;
3941
export const closeWithError = $$$hostConfig.closeWithError;
4042
export const stringToChunk = $$$hostConfig.stringToChunk;

0 commit comments

Comments
 (0)