@samitouri / QOS-React / commits / 7fc3eefd85

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,