@samitouri / QOS-React / commits / 53d07944df

[Fiber] Assign implicit debug info to used thenables (#34146)

Similar to #34137 but for Promises. This lets us pick up the debug info from a raw Promise as a child which is not covered by `_debugThenables`. Currently ChildFiber doesn't stash its thenables so we can't pick them up from devtools after the fact without some debug info added to the parent. It also lets us track some approximate start/end time of use():ed promises based on the first time we saw this particular Promise.

Sebastian Markbåge committed Aug 11, 2025 at 11:44 UTC 53d07944df70781b929f733e2059df43ca82edd3
1 file changed +30
packages/react-reconciler/src/ReactFiberThenable.js
+30
@@ -12,6 +12,7 @@ import type {
12 PendingThenable,
13 FulfilledThenable,
14 RejectedThenable,
15 + ReactIOInfo,
16 } from 'shared/ReactTypes';
17
18 import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
@@ -22,6 +23,8 @@ import {getWorkInProgressRoot} from './ReactFiberWorkLoop';
23
24 import ReactSharedInternals from 'shared/ReactSharedInternals';
25
26 +import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags';
27 +
28 import noop from 'shared/noop';
29
30 opaque type ThenableStateDev = {
@@ -154,6 +157,33 @@ export function trackUsedThenable<T>(
157 }
158 }
159
160 + if (__DEV__ && enableAsyncDebugInfo && thenable._debugInfo === undefined) {
161 + // In DEV mode if the thenable that we observed had no debug info, then we add
162 + // an inferred debug info so that we're able to track its potential I/O uniquely.
163 + // We don't know the real start time since the I/O could have started much
164 + // earlier and this could even be a cached Promise. Could be misleading.
165 + const startTime = performance.now();
166 + const displayName = thenable.displayName;
167 + const ioInfo: ReactIOInfo = {
168 + name: typeof displayName === 'string' ? displayName : 'Promise',
169 + start: startTime,
170 + end: startTime,
171 + value: (thenable: any),
172 + // We don't know the requesting owner nor stack.
173 + };
174 + // We can infer the await owner/stack lazily from where this promise ends up
175 + // used. It can be used in more than one place so we can't assign it here.
176 + thenable._debugInfo = [{awaited: ioInfo}];
177 + // Track when we resolved the Promise as the approximate end time.
178 + if (thenable.status !== 'fulfilled' && thenable.status !== 'rejected') {
179 + const trackEndTime = () => {
180 + // $FlowFixMe[cannot-write]
181 + ioInfo.end = performance.now();
182 + };
183 + thenable.then(trackEndTime, trackEndTime);
184 + }
185 + }
186 +
187 // We use an expando to track the status and result of a thenable so that we
188 // can synchronously unwrap the value. Think of this as an extension of the
189 // Promise API, or a custom interface that is a superset of Thenable.