From 53d07944df70781b929f733e2059df43ca82edd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Mon, 11 Aug 2025 11:44:05 -0400 Subject: [PATCH] [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. --- .../src/ReactFiberThenable.js | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/react-reconciler/src/ReactFiberThenable.js b/packages/react-reconciler/src/ReactFiberThenable.js index f4ae1d45b2..643be63ffa 100644 --- a/packages/react-reconciler/src/ReactFiberThenable.js +++ b/packages/react-reconciler/src/ReactFiberThenable.js @@ -12,6 +12,7 @@ import type { PendingThenable, FulfilledThenable, RejectedThenable, + ReactIOInfo, } from 'shared/ReactTypes'; import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy'; @@ -22,6 +23,8 @@ import {getWorkInProgressRoot} from './ReactFiberWorkLoop'; import ReactSharedInternals from 'shared/ReactSharedInternals'; +import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags'; + import noop from 'shared/noop'; opaque type ThenableStateDev = { @@ -154,6 +157,33 @@ export function trackUsedThenable( } } + if (__DEV__ && enableAsyncDebugInfo && thenable._debugInfo === undefined) { + // In DEV mode if the thenable that we observed had no debug info, then we add + // an inferred debug info so that we're able to track its potential I/O uniquely. + // We don't know the real start time since the I/O could have started much + // earlier and this could even be a cached Promise. Could be misleading. + const startTime = performance.now(); + const displayName = thenable.displayName; + const ioInfo: ReactIOInfo = { + name: typeof displayName === 'string' ? displayName : 'Promise', + start: startTime, + end: startTime, + value: (thenable: any), + // We don't know the requesting owner nor stack. + }; + // We can infer the await owner/stack lazily from where this promise ends up + // used. It can be used in more than one place so we can't assign it here. + thenable._debugInfo = [{awaited: ioInfo}]; + // Track when we resolved the Promise as the approximate end time. + if (thenable.status !== 'fulfilled' && thenable.status !== 'rejected') { + const trackEndTime = () => { + // $FlowFixMe[cannot-write] + ioInfo.end = performance.now(); + }; + thenable.then(trackEndTime, trackEndTime); + } + } + // We use an expando to track the status and result of a thenable so that we // can synchronously unwrap the value. Think of this as an extension of the // Promise API, or a custom interface that is a superset of Thenable.