@samitouri / QOS-React / commits / 64ed221c3d

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};