Skip to content

[Fiber] Deprecate "Throw a Promise" technique #34032

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

sebmarkbage
Copy link
Collaborator

@sebmarkbage sebmarkbage commented Jul 29, 2025

This has been replaced by React.use(promise) which in turn should be used unconditionally if data is depended upon.

The "throw a Promise" technique has many issues.

  • The main one is that since you can place it inside any other function, you can't easily detect what may or may not suspend. It's also easy to accidentally place a try/catch around it and treat it as an error instead. The use() syntax ensures that the lint rule warns you if you try/catch it or place it in a function that's not a Hook or Component.
  • There's no way to statically optimize the resumption of to be state machines like a generator or async await. We always have to rerender the stack.
  • The use() model can support just plain uncached Promises as well and potentially just plain async functions on the client.
  • For legacy reasons, throw-a-promise disables optimizations like yielding and resume in place (aka the Suspense optimization). That's due to the requirement to rerender from the top to avoid caches causing infinite ping loops.
  • This also has implications for visualizations since the Performance Track can no longer show the Suspended point inside the tree but have to show it as two separate renders. Other than just being slower.
  • We have no way of track the stack trace of where something suspended for debug information when it does suspend. In fact, that is even an issue with this warning itself. We cannot warn about which custom Hook threw the Promise. Just that something in this Component did.
  • Since it is only called conditionally if something isn't already in the cache, we cannot track the dependencies in Suspense DevTools since it would no longer be a dependency if it was already resolved. This isn't just an issue if it resolves before React happens to render it but it's also an issue just for plain resolves since we can't detect the difference between a new navigation that no longer depends on this data, vs the data just happened to load.
  • Finally, our implementation details have to all be written in a way to handle throwing a Promise anywhere you can throw an error including all kinds of edge case callbacks. Therefore our implementation, even for the other APIs like lazy and use(promise) are implemented similarly to share as much code as possible but it would be a lot simpler if they could just suspend in place where those APIs are implemented instead of in the core of the work loop.

@sebmarkbage sebmarkbage requested a review from rickhanlonii July 29, 2025 03:44
@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label Jul 29, 2025
@react-sizebot
Copy link

react-sizebot commented Jul 29, 2025

Comparing: 9be531c...4c786b6

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.11% 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 530.04 kB 530.04 kB = 93.63 kB 93.63 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB +0.16% 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 654.48 kB 654.48 kB = 115.34 kB 115.34 kB
facebook-www/ReactDOM-prod.classic.js = 674.42 kB 674.42 kB = 118.68 kB 118.68 kB
facebook-www/ReactDOM-prod.modern.js = 664.84 kB 664.84 kB = 117.02 kB 117.03 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
test_utils/ReactAllWarnings.js +0.27% 66.57 kB 66.75 kB +0.46% 16.72 kB 16.79 kB

Generated by 🚫 dangerJS against 4c786b6

@sebmarkbage sebmarkbage force-pushed the deprecatethrowapromise branch 4 times, most recently from 81d1fcb to a299ba0 Compare July 29, 2025 07:46
poteto added a commit that referenced this pull request Jul 29, 2025
Follow up to #34032. The linter now ensures that `use` cannot be used within try/catch.
poteto added a commit that referenced this pull request Jul 29, 2025
Follow up to #34032. The linter now ensures that `use` cannot be used within try/catch.
poteto added a commit that referenced this pull request Jul 29, 2025
Follow up to #34032. The linter now ensures that `use` cannot be used within try/catch.
@sebmarkbage sebmarkbage force-pushed the deprecatethrowapromise branch from a299ba0 to 4c786b6 Compare July 29, 2025 16:08
poteto added a commit that referenced this pull request Jul 29, 2025
Follow up to #34032. The linter now ensures that `use` cannot be used
within try/catch.
github-actions bot pushed a commit that referenced this pull request Jul 29, 2025
Follow up to #34032. The linter now ensures that `use` cannot be used
within try/catch.

DiffTrain build for [820af20](820af20)
github-actions bot pushed a commit that referenced this pull request Jul 29, 2025
Follow up to #34032. The linter now ensures that `use` cannot be used
within try/catch.

DiffTrain build for [820af20](820af20)
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.

3 participants