Formalize the Wakeable and Thenable types (#18391)
* Formalize the Wakeable and Thenable types We use two subsets of Promises throughout React APIs. This introduces the smallest subset - Wakeable. It's the thing that you can throw to suspend. It's something that can ping. I also use a shared type for Thenable in the cases where we expect a value so we can be a bit more rigid with our us of them. * Make Chunks into Wakeables instead of using native Promises This value is just going from here to React so we can keep it a lighter abstraction throughout. * Renamed thenable to wakeable in variable names
Sebastian Markbåge committed
Mar 25, 2020 at 16:49 UTC
64ed221c3dd44601d043236479150ac6ccf2a858
13 files changed
+160
-159
packages/react-cache/src/ReactCache.js
+2
-5
@@ -7,15 +7,12 @@
7
* @flow
8
*/
9
10
+import type {Thenable} from 'shared/ReactTypes';
11
+
12
import * as React from 'react';
13
14
import {createLRU} from './LRU';
15
14
-type Thenable<T> = {
15
- then(resolve: (T) => mixed, reject: (mixed) => mixed): mixed,
16
- ...
17
-};
18
-
16
type Suspender = {then(resolve: () => mixed, reject: () => mixed): mixed, ...};
17
18
type PendingResult = {|
packages/react-client/src/ReactFlightClient.js
+78
-74
@@ -7,6 +7,7 @@
7
* @flow
8
*/
9
10
+import type {Wakeable} from 'shared/ReactTypes';
11
import type {BlockComponent, BlockRenderFunction} from 'react/src/ReactBlock';
12
import type {LazyComponent} from 'react/src/ReactLazy';
13
@@ -39,48 +40,62 @@ const PENDING = 0;
40
const RESOLVED = 1;
41
const ERRORED = 2;
42
42
-const CHUNK_TYPE = Symbol('flight.chunk');
43
-
44
-type PendingChunk = {|
45
- $$typeof: Symbol,
46
- status: 0,
47
- value: Promise<void>,
48
- resolve: () => void,
49
-|};
50
-type ResolvedChunk<T> = {|
51
- $$typeof: Symbol,
52
- status: 1,
53
- value: T,
54
- resolve: null,
55
-|};
56
-type ErroredChunk = {|
57
- $$typeof: Symbol,
58
- status: 2,
59
- value: Error,
60
- resolve: null,
61
-|};
62
-type Chunk<T> = PendingChunk | ResolvedChunk<T> | ErroredChunk;
43
+type PendingChunk = {
44
+ _status: 0,
45
+ _value: null | Array<() => mixed>,
46
+ then(resolve: () => mixed): void,
47
+};
48
+type ResolvedChunk<T> = {
49
+ _status: 1,
50
+ _value: T,
51
+ then(resolve: () => mixed): void,
52
+};
53
+type ErroredChunk = {
54
+ _status: 2,
55
+ _value: Error,
56
+ then(resolve: () => mixed): void,
57
+};
58
+type SomeChunk<T> = PendingChunk | ResolvedChunk<T> | ErroredChunk;
59
+
60
+function Chunk(status: any, value: any) {
61
+ this._status = status;
62
+ this._value = value;
63
+}
64
+Chunk.prototype.then = function<T>(resolve: () => mixed) {
65
+ let chunk: SomeChunk<T> = this;
66
+ if (chunk._status === PENDING) {
67
+ if (chunk._value === null) {
68
+ chunk._value = [];
69
+ }
70
+ chunk._value.push(resolve);
71
+ } else {
72
+ resolve();
73
+ }
74
+};
75
76
export type Response<T> = {
77
partialRow: string,
66
- rootChunk: Chunk<T>,
67
- chunks: Map<number, Chunk<any>>,
78
+ rootChunk: SomeChunk<T>,
79
+ chunks: Map<number, SomeChunk<any>>,
80
readRoot(): T,
81
};
82
83
function readRoot<T>(): T {
84
let response: Response<T> = this;
85
let rootChunk = response.rootChunk;
74
- if (rootChunk.status === RESOLVED) {
75
- return rootChunk.value;
86
+ if (rootChunk._status === RESOLVED) {
87
+ return rootChunk._value;
88
+ } else if (rootChunk._status === PENDING) {
89
+ // eslint-disable-next-line no-throw-literal
90
+ throw (rootChunk: Wakeable);
91
} else {
77
- throw rootChunk.value;
92
+ throw rootChunk._value;
93
}
94
}
95
96
export function createResponse<T>(): Response<T> {
82
- let rootChunk: Chunk<any> = createPendingChunk();
83
- let chunks: Map<number, Chunk<any>> = new Map();
97
+ let rootChunk: SomeChunk<any> = createPendingChunk();
98
+ let chunks: Map<number, SomeChunk<any>> = new Map();
99
chunks.set(0, rootChunk);
100
let response = {
101
partialRow: '',
@@ -92,58 +107,48 @@ export function createResponse<T>(): Response<T> {
107
}
108
109
function createPendingChunk(): PendingChunk {
95
- let resolve: () => void = (null: any);
96
- let promise = new Promise(r => (resolve = r));
97
- return {
98
- $$typeof: CHUNK_TYPE,
99
- status: PENDING,
100
- value: promise,
101
- resolve: resolve,
102
- };
110
+ return new Chunk(PENDING, null);
111
}
112
113
function createErrorChunk(error: Error): ErroredChunk {
106
- return {
107
- $$typeof: CHUNK_TYPE,
108
- status: ERRORED,
109
- value: error,
110
- resolve: null,
111
- };
114
+ return new Chunk(ERRORED, error);
115
+}
116
+
117
+function wakeChunk(listeners: null | Array<() => mixed>) {
118
+ if (listeners !== null) {
119
+ for (let i = 0; i < listeners.length; i++) {
120
+ let listener = listeners[i];
121
+ listener();
122
+ }
123
+ }
124
}
125
114
-function triggerErrorOnChunk<T>(chunk: Chunk<T>, error: Error): void {
115
- if (chunk.status !== PENDING) {
126
+function triggerErrorOnChunk<T>(chunk: SomeChunk<T>, error: Error): void {
127
+ if (chunk._status !== PENDING) {
128
// We already resolved. We didn't expect to see this.
129
return;
130
}
119
- let resolve = chunk.resolve;
131
+ let listeners = chunk._value;
132
let erroredChunk: ErroredChunk = (chunk: any);
121
- erroredChunk.status = ERRORED;
122
- erroredChunk.value = error;
123
- erroredChunk.resolve = null;
124
- resolve();
133
+ erroredChunk._status = ERRORED;
134
+ erroredChunk._value = error;
135
+ wakeChunk(listeners);
136
}
137
138
function createResolvedChunk<T>(value: T): ResolvedChunk<T> {
128
- return {
129
- $$typeof: CHUNK_TYPE,
130
- status: RESOLVED,
131
- value: value,
132
- resolve: null,
133
- };
139
+ return new Chunk(RESOLVED, value);
140
}
141
136
-function resolveChunk<T>(chunk: Chunk<T>, value: T): void {
137
- if (chunk.status !== PENDING) {
142
+function resolveChunk<T>(chunk: SomeChunk<T>, value: T): void {
143
+ if (chunk._status !== PENDING) {
144
// We already resolved. We didn't expect to see this.
145
return;
146
}
141
- let resolve = chunk.resolve;
147
+ let listeners = chunk._value;
148
let resolvedChunk: ResolvedChunk<T> = (chunk: any);
143
- resolvedChunk.status = RESOLVED;
144
- resolvedChunk.value = value;
145
- resolvedChunk.resolve = null;
146
- resolve();
149
+ resolvedChunk._status = RESOLVED;
150
+ resolvedChunk._value = value;
151
+ wakeChunk(listeners);
152
}
153
154
// Report that any missing chunks in the model is now going to throw this
@@ -160,16 +165,19 @@ export function reportGlobalError<T>(
165
});
166
}
167
163
-function readMaybeChunk<T>(maybeChunk: Chunk<T> | T): T {
164
- if (maybeChunk == null || (maybeChunk: any).$$typeof !== CHUNK_TYPE) {
168
+function readMaybeChunk<T>(maybeChunk: SomeChunk<T> | T): T {
169
+ if (maybeChunk == null || !(maybeChunk instanceof Chunk)) {
170
// $FlowFixMe
171
return maybeChunk;
172
}
168
- let chunk: Chunk<T> = (maybeChunk: any);
169
- if (chunk.status === RESOLVED) {
170
- return chunk.value;
173
+ let chunk: SomeChunk<T> = (maybeChunk: any);
174
+ if (chunk._status === RESOLVED) {
175
+ return chunk._value;
176
+ } else if (chunk._status === PENDING) {
177
+ // eslint-disable-next-line no-throw-literal
178
+ throw (chunk: Wakeable);
179
} else {
172
- throw chunk.value;
180
+ throw chunk._value;
181
}
182
}
183
@@ -216,14 +224,10 @@ function createElement(type, key, props): React$Element<any> {
224
225
type UninitializedBlockPayload<Data> = [
226
mixed,
219
- ModuleMetaData | Chunk<ModuleMetaData>,
220
- Data | Chunk<Data>,
227
+ ModuleMetaData | SomeChunk<ModuleMetaData>,
228
+ Data | SomeChunk<Data>,
229
];
230
223
-type Thenable<T> = {
224
- then(resolve: (T) => mixed, reject?: (mixed) => mixed): Thenable<any>,
225
-};
226
-
231
function initializeBlock<Props, Data>(
232
tuple: UninitializedBlockPayload<Data>,
233
): BlockComponent<Props, Data> {
packages/react-devtools-shared/src/devtools/cache.js
+3
-4
@@ -7,6 +7,8 @@
7
* @flow
8
*/
9
10
+import type {Thenable} from 'shared/ReactTypes';
11
+
12
import * as React from 'react';
13
import {createContext} from 'react';
14
@@ -20,10 +22,7 @@ import {createContext} from 'react';
22
// The size of this cache is bounded by how many renders were profiled,
23
// and it will be fully reset between profiling sessions.
24
23
-export type Thenable<T> = {
24
- then(resolve: (T) => mixed, reject: (mixed) => mixed): mixed,
25
- ...
26
-};
25
+export type {Thenable};
26
27
type Suspender = {then(resolve: () => mixed, reject: () => mixed): mixed, ...};
28
packages/react-dom/src/test-utils/ReactTestUtilsAct.js
+4
-4
@@ -7,7 +7,7 @@
7
* @flow
8
*/
9
10
-import type {Thenable} from 'react-reconciler/src/ReactFiberWorkLoop';
10
+import type {Thenable} from 'shared/ReactTypes';
11
12
import * as ReactDOM from 'react-dom';
13
import ReactSharedInternals from 'shared/ReactSharedInternals';
@@ -73,7 +73,7 @@ function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
73
let actingUpdatesScopeDepth = 0;
74
let didWarnAboutUsingActInProd = false;
75
76
-function act(callback: () => Thenable) {
76
+function act(callback: () => Thenable<mixed>): Thenable<void> {
77
if (!__DEV__) {
78
if (didWarnAboutUsingActInProd === false) {
79
didWarnAboutUsingActInProd = true;
@@ -146,7 +146,7 @@ function act(callback: () => Thenable) {
146
// effects and microtasks in a loop until flushPassiveEffects() === false,
147
// and cleans up
148
return {
149
- then(resolve: () => void, reject: (?Error) => void) {
149
+ then(resolve, reject) {
150
called = true;
151
result.then(
152
() => {
@@ -206,7 +206,7 @@ function act(callback: () => Thenable) {
206
207
// in the sync case, the returned thenable only warns *if* await-ed
208
return {
209
- then(resolve: () => void) {
209
+ then(resolve) {
210
if (__DEV__) {
211
console.error(
212
'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
packages/react-flight-dom-webpack/src/ReactFlightClientWebpackBundlerConfig.js
+1
-6
@@ -22,16 +22,11 @@ export function resolveModuleReference<T>(
22
return moduleData;
23
}
24
25
-type Thenable = {
26
- then(resolve: (any) => mixed, reject?: (Error) => mixed): Thenable,
27
- ...
28
-};
29
-
25
// The chunk cache contains all the chunks we've preloaded so far.
26
// If they're still pending they're a thenable. This map also exists
27
// in Webpack but unfortunately it's not exposed so we have to
28
// replicate it in user space. null means that it has already loaded.
34
-const chunkCache: Map<string, null | Thenable | Error> = new Map();
29
+const chunkCache: Map<string, null | Promise<any> | Error> = new Map();
30
31
// Start preloading the modules since we might need them soon.
32
// This function doesn't suspend.
packages/react-reconciler/src/ReactFiberCommitWork.js
+15
-15
@@ -21,7 +21,7 @@ import type {ExpirationTime} from './ReactFiberExpirationTime';
21
import type {CapturedValue, CapturedError} from './ReactCapturedValue';
22
import type {SuspenseState} from './ReactFiberSuspenseComponent';
23
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks';
24
-import type {Thenable} from './ReactFiberWorkLoop';
24
+import type {Wakeable} from 'shared/ReactTypes';
25
import type {ReactPriorityLevel} from './SchedulerWithReactIntegration';
26
27
import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
@@ -118,7 +118,7 @@ import {
118
} from './ReactFiberHostConfig';
119
import {
120
captureCommitPhaseError,
121
- resolveRetryThenable,
121
+ resolveRetryWakeable,
122
markCommitTimeOfFallback,
123
enqueuePendingPassiveHookEffectMount,
124
enqueuePendingPassiveHookEffectUnmount,
@@ -1783,9 +1783,9 @@ function commitSuspenseComponent(finishedWork: Fiber) {
1783
if (enableSuspenseCallback && newState !== null) {
1784
const suspenseCallback = finishedWork.memoizedProps.suspenseCallback;
1785
if (typeof suspenseCallback === 'function') {
1786
- const thenables: Set<Thenable> | null = (finishedWork.updateQueue: any);
1787
- if (thenables !== null) {
1788
- suspenseCallback(new Set(thenables));
1786
+ const wakeables: Set<Wakeable> | null = (finishedWork.updateQueue: any);
1787
+ if (wakeables !== null) {
1788
+ suspenseCallback(new Set(wakeables));
1789
}
1790
} else if (__DEV__) {
1791
if (suspenseCallback !== undefined) {
@@ -1827,27 +1827,27 @@ function commitSuspenseHydrationCallbacks(
1827
}
1828
1829
function attachSuspenseRetryListeners(finishedWork: Fiber) {
1830
- // If this boundary just timed out, then it will have a set of thenables.
1831
- // For each thenable, attach a listener so that when it resolves, React
1830
+ // If this boundary just timed out, then it will have a set of wakeables.
1831
+ // For each wakeable, attach a listener so that when it resolves, React
1832
// attempts to re-render the boundary in the primary (pre-timeout) state.
1833
- const thenables: Set<Thenable> | null = (finishedWork.updateQueue: any);
1834
- if (thenables !== null) {
1833
+ const wakeables: Set<Wakeable> | null = (finishedWork.updateQueue: any);
1834
+ if (wakeables !== null) {
1835
finishedWork.updateQueue = null;
1836
let retryCache = finishedWork.stateNode;
1837
if (retryCache === null) {
1838
retryCache = finishedWork.stateNode = new PossiblyWeakSet();
1839
}
1840
- thenables.forEach(thenable => {
1840
+ wakeables.forEach(wakeable => {
1841
// Memoize using the boundary fiber to prevent redundant listeners.
1842
- let retry = resolveRetryThenable.bind(null, finishedWork, thenable);
1843
- if (!retryCache.has(thenable)) {
1842
+ let retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
1843
+ if (!retryCache.has(wakeable)) {
1844
if (enableSchedulerTracing) {
1845
- if (thenable.__reactDoNotTraceInteractions !== true) {
1845
+ if (wakeable.__reactDoNotTraceInteractions !== true) {
1846
retry = Schedule_tracing_wrap(retry);
1847
}
1848
}
1849
- retryCache.add(thenable);
1850
- thenable.then(retry, retry);
1849
+ retryCache.add(wakeable);
1850
+ wakeable.then(retry, retry);
1851
}
1852
});
1853
}
packages/react-reconciler/src/ReactFiberReconciler.old.js
+4
-4
@@ -17,7 +17,7 @@ import type {
17
PublicInstance,
18
} from './ReactFiberHostConfig';
19
import {FundamentalComponent} from './ReactWorkTags';
20
-import type {ReactNodeList} from 'shared/ReactTypes';
20
+import type {ReactNodeList, Thenable} from 'shared/ReactTypes';
21
import type {ExpirationTime} from './ReactFiberExpirationTime';
22
import type {
23
SuspenseHydrationCallbacks,
@@ -582,7 +582,7 @@ let actingUpdatesScopeDepth = 0;
582
let didWarnAboutUsingActInProd = false;
583
584
// eslint-disable-next-line no-inner-declarations
585
-export function act(callback: () => Thenable) {
585
+export function act(callback: () => Thenable<mixed>): Thenable<void> {
586
if (!__DEV__) {
587
if (didWarnAboutUsingActInProd === false) {
588
didWarnAboutUsingActInProd = true;
@@ -656,7 +656,7 @@ export function act(callback: () => Thenable) {
656
// effects and microtasks in a loop until flushPassiveEffects() === false,
657
// and cleans up
658
return {
659
- then(resolve: () => void, reject: (?Error) => void) {
659
+ then(resolve, reject) {
660
called = true;
661
result.then(
662
() => {
@@ -716,7 +716,7 @@ export function act(callback: () => Thenable) {
716
717
// in the sync case, the returned thenable only warns *if* await-ed
718
return {
719
- then(resolve: () => void) {
719
+ then(resolve) {
720
if (__DEV__) {
721
console.error(
722
'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
packages/react-reconciler/src/ReactFiberRoot.js
+3
-3
@@ -11,7 +11,7 @@ import type {Fiber} from './ReactFiber';
11
import type {ExpirationTime} from './ReactFiberExpirationTime';
12
import type {RootTag} from 'react-reconciler/src/ReactRootTags';
13
import type {TimeoutHandle, NoTimeout} from './ReactFiberHostConfig';
14
-import type {Thenable} from './ReactFiberWorkLoop';
14
+import type {Wakeable} from 'shared/ReactTypes';
15
import type {Interaction} from 'scheduler/src/Tracing';
16
import type {SuspenseHydrationCallbacks} from './ReactFiberSuspenseComponent';
17
import type {ReactPriorityLevel} from './SchedulerWithReactIntegration';
@@ -42,8 +42,8 @@ type BaseFiberRootProperties = {|
42
current: Fiber,
43
44
pingCache:
45
- | WeakMap<Thenable, Set<ExpirationTime>>
46
- | Map<Thenable, Set<ExpirationTime>>
45
+ | WeakMap<Wakeable, Set<ExpirationTime>>
46
+ | Map<Wakeable, Set<ExpirationTime>>
47
| null,
48
49
finishedExpirationTime: ExpirationTime,
packages/react-reconciler/src/ReactFiberThrow.js
+14
-14
@@ -12,7 +12,7 @@ import type {FiberRoot} from './ReactFiberRoot';
12
import type {ExpirationTime} from './ReactFiberExpirationTime';
13
import type {CapturedValue} from './ReactCapturedValue';
14
import type {Update} from './ReactUpdateQueue';
15
-import type {Thenable} from './ReactFiberWorkLoop';
15
+import type {Wakeable} from 'shared/ReactTypes';
16
import type {SuspenseContext} from './ReactFiberSuspenseContext';
17
18
import getComponentName from 'shared/getComponentName';
@@ -143,7 +143,7 @@ function createClassErrorUpdate(
143
function attachPingListener(
144
root: FiberRoot,
145
renderExpirationTime: ExpirationTime,
146
- thenable: Thenable,
146
+ wakeable: Wakeable,
147
) {
148
// Attach a listener to the promise to "ping" the root and retry. But
149
// only if one does not already exist for the current render expiration
@@ -153,12 +153,12 @@ function attachPingListener(
153
if (pingCache === null) {
154
pingCache = root.pingCache = new PossiblyWeakMap();
155
threadIDs = new Set();
156
- pingCache.set(thenable, threadIDs);
156
+ pingCache.set(wakeable, threadIDs);
157
} else {
158
- threadIDs = pingCache.get(thenable);
158
+ threadIDs = pingCache.get(wakeable);
159
if (threadIDs === undefined) {
160
threadIDs = new Set();
161
- pingCache.set(thenable, threadIDs);
161
+ pingCache.set(wakeable, threadIDs);
162
}
163
}
164
if (!threadIDs.has(renderExpirationTime)) {
@@ -167,10 +167,10 @@ function attachPingListener(
167
let ping = pingSuspendedRoot.bind(
168
null,
169
root,
170
- thenable,
170
+ wakeable,
171
renderExpirationTime,
172
);
173
- thenable.then(ping, ping);
173
+ wakeable.then(ping, ping);
174
}
175
}
176
@@ -191,8 +191,8 @@ function throwException(
191
typeof value === 'object' &&
192
typeof value.then === 'function'
193
) {
194
- // This is a thenable.
195
- const thenable: Thenable = (value: any);
194
+ // This is a wakeable.
195
+ const wakeable: Wakeable = (value: any);
196
197
if ((sourceFiber.mode & BlockingMode) === NoMode) {
198
// Reset the memoizedState to what it was before we attempted
@@ -224,13 +224,13 @@ function throwException(
224
225
// Stash the promise on the boundary fiber. If the boundary times out, we'll
226
// attach another listener to flip the boundary back to its normal state.
227
- const thenables: Set<Thenable> = (workInProgress.updateQueue: any);
228
- if (thenables === null) {
227
+ const wakeables: Set<Wakeable> = (workInProgress.updateQueue: any);
228
+ if (wakeables === null) {
229
const updateQueue = (new Set(): any);
230
- updateQueue.add(thenable);
230
+ updateQueue.add(wakeable);
231
workInProgress.updateQueue = updateQueue;
232
} else {
233
- thenables.add(thenable);
233
+ wakeables.add(wakeable);
234
}
235
236
// If the boundary is outside of blocking mode, we should *not*
@@ -316,7 +316,7 @@ function throwException(
316
// We want to ensure that a "busy" state doesn't get force committed. We want to
317
// ensure that new initial loading states can commit as soon as possible.
318
319
- attachPingListener(root, renderExpirationTime, thenable);
319
+ attachPingListener(root, renderExpirationTime, wakeable);
320
321
workInProgress.effectTag |= ShouldCapture;
322
workInProgress.expirationTime = renderExpirationTime;
packages/react-reconciler/src/ReactFiberWorkLoop.js
+8
-14
@@ -7,6 +7,7 @@
7
* @flow
8
*/
9
10
+import type {Wakeable} from 'shared/ReactTypes';
11
import type {Fiber} from './ReactFiber';
12
import type {FiberRoot} from './ReactFiberRoot';
13
import type {ExpirationTime} from './ReactFiberExpirationTime';
@@ -215,13 +216,6 @@ const RootSuspended = 3;
216
const RootSuspendedWithDelay = 4;
217
const RootCompleted = 5;
218
218
-export type Thenable = {
219
- then(resolve: () => mixed, reject?: () => mixed): Thenable | void,
220
- // Special flag to opt out of tracing interactions across a Suspense boundary.
221
- __reactDoNotTraceInteractions?: boolean,
222
- ...
223
-};
224
-
219
// Describes where we are in the React execution stack
220
let executionContext: ExecutionContext = NoContext;
221
// The root we're working on
@@ -2543,14 +2537,14 @@ export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
2537
2538
export function pingSuspendedRoot(
2539
root: FiberRoot,
2546
- thenable: Thenable,
2540
+ wakeable: Wakeable,
2541
suspendedTime: ExpirationTime,
2542
) {
2543
const pingCache = root.pingCache;
2544
if (pingCache !== null) {
2551
- // The thenable resolved, so we no longer need to memoize, because it will
2545
+ // The wakeable resolved, so we no longer need to memoize, because it will
2546
// never be thrown again.
2553
- pingCache.delete(thenable);
2547
+ pingCache.delete(wakeable);
2548
}
2549
2550
if (workInProgressRoot === root && renderExpirationTime === suspendedTime) {
@@ -2636,9 +2630,9 @@ export function retryDehydratedSuspenseBoundary(boundaryFiber: Fiber) {
2630
retryTimedOutBoundary(boundaryFiber, retryTime);
2631
}
2632
2639
-export function resolveRetryThenable(boundaryFiber: Fiber, thenable: Thenable) {
2633
+export function resolveRetryWakeable(boundaryFiber: Fiber, wakeable: Wakeable) {
2634
let retryTime = NoWork; // Default
2641
- let retryCache: WeakSet<Thenable> | Set<Thenable> | null;
2635
+ let retryCache: WeakSet<Wakeable> | Set<Wakeable> | null;
2636
if (enableSuspenseServerRenderer) {
2637
switch (boundaryFiber.tag) {
2638
case SuspenseComponent:
@@ -2663,9 +2657,9 @@ export function resolveRetryThenable(boundaryFiber: Fiber, thenable: Thenable) {
2657
}
2658
2659
if (retryCache !== null) {
2666
- // The thenable resolved, so we no longer need to memoize, because it will
2660
+ // The wakeable resolved, so we no longer need to memoize, because it will
2661
// never be thrown again.
2668
- retryCache.delete(thenable);
2662
+ retryCache.delete(wakeable);
2663
}
2664
2665
retryTimedOutBoundary(boundaryFiber, retryTime);
packages/react/src/ReactLazy.js
+9
-11
@@ -7,11 +7,9 @@
7
* @flow
8
*/
9
10
-import {REACT_LAZY_TYPE} from 'shared/ReactSymbols';
10
+import type {Wakeable, Thenable} from 'shared/ReactTypes';
11
12
-type Thenable<T, R> = {
13
- then(resolve: (T) => mixed, reject: (mixed) => mixed): R,
14
-};
12
+import {REACT_LAZY_TYPE} from 'shared/ReactSymbols';
13
14
const Uninitialized = -1;
15
const Pending = 0;
@@ -20,12 +18,12 @@ const Rejected = 2;
18
19
type UninitializedPayload<T> = {
20
_status: -1,
23
- _result: () => Thenable<{default: T, ...}, mixed>,
21
+ _result: () => Thenable<{default: T, ...}>,
22
};
23
26
-type PendingPayload<T> = {
24
+type PendingPayload = {
25
_status: 0,
28
- _result: Thenable<{default: T, ...}, mixed>,
26
+ _result: Wakeable,
27
};
28
29
type ResolvedPayload<T> = {
@@ -40,7 +38,7 @@ type RejectedPayload = {
38
39
type Payload<T> =
40
| UninitializedPayload<T>
43
- | PendingPayload<T>
41
+ | PendingPayload
42
| ResolvedPayload<T>
43
| RejectedPayload;
44
@@ -55,7 +53,7 @@ function lazyInitializer<T>(payload: Payload<T>): T {
53
const ctor = payload._result;
54
const thenable = ctor();
55
// Transition to the next state.
58
- const pending: PendingPayload<any> = (payload: any);
56
+ const pending: PendingPayload = (payload: any);
57
pending._status = Pending;
58
pending._result = thenable;
59
thenable.then(
@@ -75,7 +73,7 @@ function lazyInitializer<T>(payload: Payload<T>): T {
73
}
74
}
75
// Transition to the next state.
78
- const resolved: ResolvedPayload<any> = (payload: any);
76
+ const resolved: ResolvedPayload<T> = (payload: any);
77
resolved._status = Resolved;
78
resolved._result = defaultExport;
79
}
@@ -98,7 +96,7 @@ function lazyInitializer<T>(payload: Payload<T>): T {
96
}
97
98
export function lazy<T>(
101
- ctor: () => Thenable<{default: T, ...}, mixed>,
99
+ ctor: () => Thenable<{default: T, ...}>,
100
): LazyComponent<T, Payload<T>> {
101
let payload: Payload<T> = {
102
// We use these fields to store the result.
packages/shared/ReactTypes.js
+18
@@ -234,3 +234,21 @@ export type MutableSource<Source: $NonMaybeType<mixed>> = {|
234
_currentPrimaryRenderer?: Object | null,
235
_currentSecondaryRenderer?: Object | null,
236
|};
237
+
238
+// The subset of a Thenable required by things thrown by Suspense.
239
+// This doesn't require a value to be passed to either handler.
240
+export interface Wakeable {
241
+ then(onFulfill: () => mixed, onReject: () => mixed): void | Wakeable;
242
+ // Special flag to opt out of tracing interactions across a Suspense boundary.
243
+ __reactDoNotTraceInteractions?: boolean;
244
+}
245
+
246
+// The subset of a Promise that React APIs rely on. This resolves a value.
247
+// This doesn't require a return value neither from the handler nor the
248
+// then function.
249
+export interface Thenable<+R> {
250
+ then<U>(
251
+ onFulfill: (value: R) => void | Thenable<U> | U,
252
+ onReject: (error: mixed) => void | Thenable<U> | U,
253
+ ): void | Thenable<U>;
254
+}
scripts/flow/environment.js
+1
-5
@@ -67,9 +67,5 @@ declare module 'EventListener' {
67
};
68
}
69
70
-type Thenable = {
71
- then(resolve: (mixed) => mixed, reject?: (Error) => mixed): Thenable,
72
-};
73
-
74
-declare function __webpack_chunk_load__(id: string): Thenable;
70
+declare function __webpack_chunk_load__(id: string): Promise<mixed>;
71
declare function __webpack_require__(id: string): {default: any};