From 4c786b623beaae5266556f593d64798d5c1d9d31 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 28 Jul 2025 23:27:40 -0400 Subject: [PATCH] Deprecate throw-a-Promise --- .../shouldIgnoreConsoleWarn.js | 8 +++++ .../src/ReactFiberWorkLoop.js | 35 ++++++++++++++----- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/packages/internal-test-utils/shouldIgnoreConsoleWarn.js b/packages/internal-test-utils/shouldIgnoreConsoleWarn.js index cde6264e01352..7aeb5208cf66a 100644 --- a/packages/internal-test-utils/shouldIgnoreConsoleWarn.js +++ b/packages/internal-test-utils/shouldIgnoreConsoleWarn.js @@ -1,5 +1,13 @@ 'use strict'; module.exports = function shouldIgnoreConsoleWarn(format) { + if ( + typeof format === 'string' && + format.startsWith( + 'Throwing a Promise to cause it to suspend is deprecated in React', + ) + ) { + return true; + } return false; }; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 1107c5bd4624d..6c06b47f49364 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -2013,6 +2013,8 @@ function resetSuspendedWorkLoopOnUnwind(fiber: Fiber) { resetChildReconcilerOnUnwind(); } +let didWarnForThrowAPromise = false; + function handleThrow(root: FiberRoot, thrownValue: any): void { // A component threw an exception. Usually this is because it suspended, but // it also includes regular program errors. @@ -2066,19 +2068,34 @@ function handleThrow(root: FiberRoot, thrownValue: any): void { // case where we think this should happen. workInProgressSuspendedReason = SuspendedOnHydration; } else { - // This is a regular error. const isWakeable = thrownValue !== null && typeof thrownValue === 'object' && typeof thrownValue.then === 'function'; - - workInProgressSuspendedReason = isWakeable - ? // A wakeable object was thrown by a legacy Suspense implementation. - // This has slightly different behavior than suspending with `use`. - SuspendedOnDeprecatedThrowPromise - : // This is a regular error. If something earlier in the component already - // suspended, we must clear the thenable state to unblock the work loop. - SuspendedOnError; + if (isWakeable) { + // A wakeable object was thrown by a legacy Suspense implementation. + // This has slightly different behavior than suspending with `use`. + workInProgressSuspendedReason = SuspendedOnDeprecatedThrowPromise; + if (__DEV__) { + if (!didWarnForThrowAPromise && workInProgress !== null) { + didWarnForThrowAPromise = true; + const componentName = + getComponentNameFromFiber(workInProgress) || 'unknown'; + runWithFiberInDEV(workInProgress, () => { + console.warn( + 'Throwing a Promise to cause it to suspend is deprecated in React.\n' + + 'Please update your library to call use(promise) instead.\n' + + 'See https://react.dev/reference/react/use\n\n in %s', + componentName, + ); + }); + } + } + } else { + // This is a regular error. If something earlier in the component already + // suspended, we must clear the thenable state to unblock the work loop. + workInProgressSuspendedReason = SuspendedOnError; + } } workInProgressThrownValue = thrownValue;