@samitouri / QOS-React-1 / commits / db71391c5c

[Fiber] Instrument the lazy initializer thenable in all cases (#35521)

When a lazy element or component is initialized a thenable is returned which was only be conditionally instrumented in dev when asyncDebugInfo was enabled. When instrumented these thenables can be used in conjunction with the SuspendOnImmediate optimization where if a thenable resolves before the stack unwinds we can continue rendering from the last suspended fiber. Without this change a recent fix to the useId implementation cannot be easily tested in production because this optimization pathway isn't available to regular React.lazy thenables. To land the prior PR I changed the thenables to a custom type so I could instrument manually in the test. WIth this change we can just use a regular Promise since ReactLazy will instrument in all environments/flags now

Josh Story committed Jan 15, 2026 at 19:05 UTC db71391c5c70dc113560d1c23d0b6548604d827f
2 files changed +25 -34
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+7 -20
@@ -9551,33 +9551,20 @@ Unfortunately that previous paragraph wasn't quite long enough so I'll continue
9551
9552 // Create fresh lazy components for CLIENT
9553 let resolveClientInner;
9554 - const clientLazyInner = React.lazy(() => {
9554 + const clientLazyInner = React.lazy(async () => {
9555 Scheduler.log('client lazy inner initializer');
9556 - const payload = {default: <InnerComponent />};
9557 - const promise = new Promise(r => {
9558 - resolveClientInner = () => {
9559 - promise.status = 'fulfilled';
9560 - promise.value = payload;
9561 - r(payload);
9562 - };
9556 + return new Promise(r => {
9557 + resolveClientInner = () => r({default: <InnerComponent />});
9558 });
9564 - return promise;
9559 });
9560
9561 let resolveClientOuter;
9568 - const clientLazyOuter = React.lazy(() => {
9562 + const clientLazyOuter = React.lazy(async () => {
9563 Scheduler.log('client lazy outer initializer');
9570 - const payload = {
9571 - default: <OuterComponent innerElement={clientLazyInner} />,
9572 - };
9573 - const promise = new Promise(r => {
9574 - resolveClientOuter = () => {
9575 - promise.status = 'fulfilled';
9576 - promise.value = payload;
9577 - r(payload);
9578 - };
9564 + return new Promise(r => {
9565 + resolveClientOuter = () =>
9566 + r({default: <OuterComponent innerElement={clientLazyInner} />});
9567 });
9580 - return promise;
9568 });
9569
9570 const hydrationErrors = [];
packages/react/src/ReactLazy.js
+18 -14
@@ -117,13 +117,15 @@ function lazyInitializer<T>(payload: Payload<T>): T {
117 // $FlowFixMe
118 ioInfo.value.value = debugValue;
119 }
120 - // Make the thenable introspectable
121 - if (thenable.status === undefined) {
122 - const fulfilledThenable: FulfilledThenable<{default: T, ...}> =
123 - (thenable: any);
124 - fulfilledThenable.status = 'fulfilled';
125 - fulfilledThenable.value = moduleObject;
126 - }
120 + }
121 + // Make the thenable introspectable
122 + // TODO we should move the lazy introspection into the resolveLazy
123 + // impl or make suspendedThenable be able to be a lazy itself
124 + if (thenable.status === undefined) {
125 + const fulfilledThenable: FulfilledThenable<{default: T, ...}> =
126 + (thenable: any);
127 + fulfilledThenable.status = 'fulfilled';
128 + fulfilledThenable.value = moduleObject;
129 }
130 }
131 },
@@ -151,13 +153,15 @@ function lazyInitializer<T>(payload: Payload<T>): T {
153 // $FlowFixMe
154 ioInfo.value.reason = error;
155 }
154 - // Make the thenable introspectable
155 - if (thenable.status === undefined) {
156 - const rejectedThenable: RejectedThenable<{default: T, ...}> =
157 - (thenable: any);
158 - rejectedThenable.status = 'rejected';
159 - rejectedThenable.reason = error;
160 - }
156 + }
157 + // Make the thenable introspectable
158 + // TODO we should move the lazy introspection into the resolveLazy
159 + // impl or make suspendedThenable be able to be a lazy itself
160 + if (thenable.status === undefined) {
161 + const rejectedThenable: RejectedThenable<{default: T, ...}> =
162 + (thenable: any);
163 + rejectedThenable.status = 'rejected';
164 + rejectedThenable.reason = error;
165 }
166 }
167 },