Revert yieldy behavior for non-use Suspense (in Flight, too)
Same as #25537 but for Flight. I was going to wait to do this later because the temporary implementation of async components uses some of the same code that non-used wakables do, but it's not so bad. I just had to inline one bit of code, which we'll remove when we unify the implementation with `use`.
Andrew Clark committed
Oct 22, 2022 at 22:52 UTC
7fc3eefd859611ef4d13329039c695520cfe8ad3
2 files changed
+45
-33
packages/react-server/src/ReactFlightServer.js
+39
-10
@@ -22,6 +22,9 @@ import type {
22
ServerContextJSONValue,
23
Wakeable,
24
Thenable,
25
+ PendingThenable,
26
+ FulfilledThenable,
27
+ RejectedThenable,
28
} from 'shared/ReactTypes';
29
import type {LazyComponent} from 'react/src/ReactLazy';
30
@@ -66,7 +69,6 @@ import {
69
getActiveContext,
70
rootContextSnapshot,
71
} from './ReactFlightNewContext';
69
-import {trackSuspendedWakeable} from './ReactFlightThenable';
72
73
import {
74
REACT_ELEMENT_TYPE,
@@ -224,10 +226,44 @@ function readThenable<T>(thenable: Thenable<T>): T {
226
}
227
228
function createLazyWrapperAroundWakeable(wakeable: Wakeable) {
227
- trackSuspendedWakeable(wakeable);
229
+ // This is a temporary fork of the `use` implementation until we accept
230
+ // promises everywhere.
231
+ const thenable: Thenable<mixed> = (wakeable: any);
232
+ switch (thenable.status) {
233
+ case 'fulfilled':
234
+ case 'rejected':
235
+ break;
236
+ default: {
237
+ if (typeof thenable.status === 'string') {
238
+ // Only instrument the thenable if the status if not defined. If
239
+ // it's defined, but an unknown value, assume it's been instrumented by
240
+ // some custom userspace implementation. We treat it as "pending".
241
+ break;
242
+ }
243
+ const pendingThenable: PendingThenable<mixed> = (thenable: any);
244
+ pendingThenable.status = 'pending';
245
+ pendingThenable.then(
246
+ fulfilledValue => {
247
+ if (thenable.status === 'pending') {
248
+ const fulfilledThenable: FulfilledThenable<mixed> = (thenable: any);
249
+ fulfilledThenable.status = 'fulfilled';
250
+ fulfilledThenable.value = fulfilledValue;
251
+ }
252
+ },
253
+ (error: mixed) => {
254
+ if (thenable.status === 'pending') {
255
+ const rejectedThenable: RejectedThenable<mixed> = (thenable: any);
256
+ rejectedThenable.status = 'rejected';
257
+ rejectedThenable.reason = error;
258
+ }
259
+ },
260
+ );
261
+ break;
262
+ }
263
+ }
264
const lazyType: LazyComponent<any, Thenable<any>> = {
265
$$typeof: REACT_LAZY_TYPE,
230
- _payload: (wakeable: any),
266
+ _payload: thenable,
267
_init: readThenable,
268
};
269
return lazyType;
@@ -818,11 +854,7 @@ export function resolveModelToJSON(
854
);
855
const ping = newTask.ping;
856
x.then(ping, ping);
821
-
822
- const wakeable: Wakeable = x;
823
- trackSuspendedWakeable(wakeable);
857
newTask.thenableState = getThenableStateAfterSuspending();
825
-
858
return serializeByRefID(newTask.id);
859
} else {
860
// Something errored. We'll still send everything we have up until this point.
@@ -1146,9 +1178,6 @@ function retryTask(request: Request, task: Task): void {
1178
// Something suspended again, let's pick it back up later.
1179
const ping = task.ping;
1180
x.then(ping, ping);
1149
-
1150
- const wakeable: Wakeable = x;
1151
- trackSuspendedWakeable(wakeable);
1181
task.thenableState = getThenableStateAfterSuspending();
1182
return;
1183
} else {
packages/react-server/src/ReactFlightThenable.js
+6
-23
@@ -14,7 +14,6 @@
14
// instead of "Wakeable". Or some other more appropriate name.
15
16
import type {
17
- Wakeable,
17
Thenable,
18
PendingThenable,
19
FulfilledThenable,
@@ -30,14 +29,12 @@ export function createThenableState(): ThenableState {
29
return [];
30
}
31
33
-// TODO: Unify this with trackSuspendedThenable. It needs to support not only
34
-// `use`, but async components, too.
35
-export function trackSuspendedWakeable(wakeable: Wakeable) {
36
- // If this wakeable isn't already a thenable, turn it into one now. Then,
37
- // when we resume the work loop, we can check if its status is
38
- // still pending.
39
- // TODO: Get rid of the Wakeable type? It's superseded by UntrackedThenable.
40
- const thenable: Thenable<mixed> = (wakeable: any);
32
+export function trackUsedThenable<T>(
33
+ thenableState: ThenableState,
34
+ thenable: Thenable<T>,
35
+ index: number,
36
+) {
37
+ thenableState[index] = thenable;
38
39
// We use an expando to track the status and result of a thenable so that we
40
// can synchronously unwrap the value. Think of this as an extension of the
@@ -84,20 +81,6 @@ export function trackSuspendedWakeable(wakeable: Wakeable) {
81
}
82
}
83
87
-export function trackUsedThenable<T>(
88
- thenableState: ThenableState,
89
- thenable: Thenable<T>,
90
- index: number,
91
-) {
92
- // This is only a separate function from trackSuspendedWakeable for symmetry
93
- // with Fiber.
94
- // TODO: Disallow throwing a thenable directly. It must go through `use` (or
95
- // some equivalent for internal Suspense implementations). We can't do this in
96
- // Fiber yet because it's a breaking change but we can do it in Server
97
- // Components because Server Components aren't released yet.
98
- thenableState[index] = thenable;
99
-}
100
-
84
export function getPreviouslyUsedThenableAtIndex<T>(
85
thenableState: ThenableState | null,
86
index: number,