Skip to content

[Flight] Send the awaited Promise to the client as additional debug information #33592

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 23, 2025

Conversation

sebmarkbage
Copy link
Collaborator

Stacked on #33588, #33589 and #33590.

This lets us automatically show the resolved value in the UI.

Screenshot 2025-06-22 at 12 54 41 AM

We can also show rejected I/O that may or may not have been handled with the error message.

Screenshot 2025-06-22 at 12 55 06 AM

To get this working we need to keep the Promise around for longer so that we can access it once we want to emit an async sequence. I do this by storing the WeakRefs but to ensure that the Promise doesn't get garbage collected, I keep a WeakMap of Promise to the Promise that it depended on. This lets the VM still clean up any Promise chains that have leaves that are cleaned up. So this makes Promises live until the last Promise downstream is done. At that point we can go back up the chain to read the values out of them.

Additionally, to get the best possible value we don't want to get a Promise that's used by internals of a third-party function. We want the value that the first party gets to observe. To do this I had to change the logic for which "await" to use, to be the one that is the first await that happened in user space. It's not enough that the await has any first party at all on the stack - it has to be the very first frame. This is a little sketchy because it relies on the .then() call or await call not having any third party wrappers. But it gives the best object since it hides all the internals. For example when you call fetch() we now log that actual Response object.

@sebmarkbage sebmarkbage requested a review from eps1lon June 22, 2025 05:07
@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label Jun 22, 2025
@react-sizebot
Copy link

react-sizebot commented Jun 22, 2025

Comparing: 18ee505...15d22ad

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.68 kB 6.68 kB +0.05% 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 530.57 kB 530.57 kB = 93.67 kB 93.67 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB = 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 651.66 kB 651.66 kB = 114.78 kB 114.78 kB
facebook-www/ReactDOM-prod.classic.js = 674.81 kB 674.81 kB = 118.78 kB 118.78 kB
facebook-www/ReactDOM-prod.modern.js = 665.30 kB 665.30 kB = 117.19 kB 117.19 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.development.js +8.19% 117.71 kB 127.36 kB +7.59% 21.75 kB 23.40 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-client.browser.development.js +8.17% 118.07 kB 127.71 kB +7.76% 21.69 kB 23.37 kB
oss-experimental/react-client/cjs/react-client-flight.development.js +8.08% 119.36 kB 129.01 kB +7.65% 21.51 kB 23.16 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js +8.04% 119.90 kB 129.54 kB +7.44% 22.14 kB 23.79 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js +8.01% 120.45 kB 130.10 kB +7.44% 22.27 kB 23.93 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.node.development.js +7.89% 122.31 kB 131.95 kB +6.90% 22.56 kB 24.11 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-client.edge.development.js +7.80% 123.60 kB 133.24 kB +6.77% 22.91 kB 24.46 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-client.node.development.js +7.73% 124.84 kB 134.48 kB +6.71% 23.04 kB 24.58 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.development.js +7.61% 126.69 kB 136.34 kB +6.63% 23.40 kB 24.96 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js +7.61% 126.78 kB 136.43 kB +6.62% 23.44 kB 24.99 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.development.js +7.55% 127.76 kB 137.41 kB +6.57% 23.53 kB 25.08 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.unbundled.development.js +7.41% 130.12 kB 139.76 kB +6.50% 23.78 kB 25.32 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.development.js +7.34% 131.45 kB 141.09 kB +6.43% 24.03 kB 25.57 kB
oss-experimental/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +5.34% 155.76 kB 164.08 kB +3.51% 36.15 kB 37.42 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.development.js +8.19% 117.71 kB 127.36 kB +7.59% 21.75 kB 23.40 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-client.browser.development.js +8.17% 118.07 kB 127.71 kB +7.76% 21.69 kB 23.37 kB
oss-experimental/react-client/cjs/react-client-flight.development.js +8.08% 119.36 kB 129.01 kB +7.65% 21.51 kB 23.16 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js +8.04% 119.90 kB 129.54 kB +7.44% 22.14 kB 23.79 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js +8.01% 120.45 kB 130.10 kB +7.44% 22.27 kB 23.93 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.node.development.js +7.89% 122.31 kB 131.95 kB +6.90% 22.56 kB 24.11 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-client.edge.development.js +7.80% 123.60 kB 133.24 kB +6.77% 22.91 kB 24.46 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-client.node.development.js +7.73% 124.84 kB 134.48 kB +6.71% 23.04 kB 24.58 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.development.js +7.61% 126.69 kB 136.34 kB +6.63% 23.40 kB 24.96 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js +7.61% 126.78 kB 136.43 kB +6.62% 23.44 kB 24.99 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.development.js +7.55% 127.76 kB 137.41 kB +6.57% 23.53 kB 25.08 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.unbundled.development.js +7.41% 130.12 kB 139.76 kB +6.50% 23.78 kB 25.32 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.development.js +7.34% 131.45 kB 141.09 kB +6.43% 24.03 kB 25.57 kB
oss-experimental/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +5.34% 155.76 kB 164.08 kB +3.51% 36.15 kB 37.42 kB
oss-experimental/react-markup/cjs/react-markup.react-server.development.js +1.63% 595.21 kB 604.91 kB +1.45% 106.23 kB 107.77 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +0.70% 177.32 kB 178.57 kB +0.63% 32.88 kB 33.09 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +0.68% 181.99 kB 183.23 kB +0.63% 33.34 kB 33.55 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +0.66% 188.83 kB 190.07 kB +0.66% 34.44 kB 34.66 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +0.65% 189.98 kB 191.22 kB +0.65% 34.73 kB 34.95 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +0.65% 190.03 kB 191.27 kB +0.64% 34.74 kB 34.96 kB

Generated by 🚫 dangerJS against 15d22ad

The WeakRef keeps it from retaining itself in a cycle.

However, we create a chain of dependencies between Promise instances so
that the child retains the parent so that in practice the WeakRefs are kept
alive as long as we need them which makes it safe to extract debugInfo
from them later.
This lets the client inspect the value or reason it rejected.
This is better context than the internal Promise of the third party implementation.

To do this we need to find the first await in user space and not just the
first await that has any call frames in user space. To do this we need to
find the first frame outside of the async hooks instrumentation. We do that
by hard coding how many extra frames Node currently adds (fairly stable).

The problem with this approach is that it doesn't work if someone adds a
third party wrapper around .then() like we do in our ReactPromises atm.
It's fine if you await it but not if you call it directly.
When printing common classes it gets noisy to show internals.
Copy link
Collaborator

@unstubbable unstubbable left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Somehow my question got lost when I submitted my first review.)

// If the thing we're waiting on is another Await we still track that sequence
// so that we can later pick the best stack trace in user space.
node = ({
tag: UNRESOLVED_AWAIT_NODE,
owner: resolveOwner(),
debugInfo: new WeakRef((resource: Promise<any>)),
stack: parseStackTrace(new Error(), 1),
stack: parseStackTrace(new Error(), 5),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't the 5 become incorrect when Node.js internals change between versions? Should we ignore node:internal/async_hooks frames instead (plus skip the first frame)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed I had that in an earlier version but decided against it.

Yes it could change. Last time it was changed was 6 years ago and before that it didn't change since it was added.

However, it could also just as well change file names. We're knowingly dealing with internals here regardless.

They could also just remove the API all together since it's deprecated. Hopefully our usage can inform the shape of a better replacement API.

Copy link

@hardanishsingh1 hardanishsingh1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@sebmarkbage sebmarkbage merged commit 2a911f2 into facebook:main Jun 23, 2025
241 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants