@@ -1936,6 +1936,42 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
19361936
19371937 do {
19381938 try {
1939+ if (
1940+ workInProgressSuspendedReason !== NotSuspended &&
1941+ workInProgress !== null
1942+ ) {
1943+ // The work loop is suspended. We need to either unwind the stack or
1944+ // replay the suspended component.
1945+ const unitOfWork = workInProgress ;
1946+ const thrownValue = workInProgressThrownValue ;
1947+ workInProgressSuspendedReason = NotSuspended ;
1948+ workInProgressThrownValue = null ;
1949+
1950+ // TODO: This check is only here to account for thenables that
1951+ // synchronously resolve. Otherwise we would always unwind when
1952+ // rendering with renderRootSync. (In the future, discrete updates will
1953+ // use renderRootConcurrent instead.) We should account for
1954+ // synchronously resolved thenables before hitting this path.
1955+ switch ( workInProgressSuspendedReason ) {
1956+ case SuspendedOnError : {
1957+ // Unwind then continue with the normal work loop.
1958+ unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
1959+ break ;
1960+ }
1961+ default : {
1962+ const wasPinged =
1963+ workInProgressSuspendedThenableState !== null &&
1964+ isThenableStateResolved ( workInProgressSuspendedThenableState ) ;
1965+ if ( wasPinged ) {
1966+ replaySuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
1967+ } else {
1968+ unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
1969+ }
1970+ // Continue with the normal work loop.
1971+ break ;
1972+ }
1973+ }
1974+ }
19391975 workLoopSync ( ) ;
19401976 break ;
19411977 } catch ( thrownValue ) {
@@ -1980,18 +2016,6 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
19802016/** @noinline */
19812017function workLoopSync ( ) {
19822018 // Perform work without checking if we need to yield between fiber.
1983-
1984- if ( workInProgressSuspendedReason !== NotSuspended ) {
1985- // The current work-in-progress was already attempted. We need to unwind
1986- // it before we continue the normal work loop.
1987- const thrownValue = workInProgressThrownValue ;
1988- workInProgressSuspendedReason = NotSuspended ;
1989- workInProgressThrownValue = null ;
1990- if ( workInProgress !== null ) {
1991- resumeSuspendedUnitOfWork ( workInProgress , thrownValue ) ;
1992- }
1993- }
1994-
19952019 while ( workInProgress !== null ) {
19962020 performUnitOfWork ( workInProgress ) ;
19972021 }
@@ -2039,6 +2063,36 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
20392063
20402064 do {
20412065 try {
2066+ if (
2067+ workInProgressSuspendedReason !== NotSuspended &&
2068+ workInProgress !== null
2069+ ) {
2070+ // The work loop is suspended. We need to either unwind the stack or
2071+ // replay the suspended component.
2072+ const unitOfWork = workInProgress ;
2073+ const thrownValue = workInProgressThrownValue ;
2074+ workInProgressSuspendedReason = NotSuspended ;
2075+ workInProgressThrownValue = null ;
2076+ switch ( workInProgressSuspendedReason ) {
2077+ case SuspendedOnError : {
2078+ // Unwind then continue with the normal work loop.
2079+ unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2080+ break ;
2081+ }
2082+ default : {
2083+ const wasPinged =
2084+ workInProgressSuspendedThenableState !== null &&
2085+ isThenableStateResolved ( workInProgressSuspendedThenableState ) ;
2086+ if ( wasPinged ) {
2087+ replaySuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2088+ } else {
2089+ unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2090+ }
2091+ // Continue with the normal work loop.
2092+ break ;
2093+ }
2094+ }
2095+ }
20422096 workLoopConcurrent ( ) ;
20432097 break ;
20442098 } catch ( thrownValue ) {
@@ -2091,18 +2145,6 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
20912145/** @noinline */
20922146function workLoopConcurrent ( ) {
20932147 // Perform work until Scheduler asks us to yield
2094-
2095- if ( workInProgressSuspendedReason !== NotSuspended ) {
2096- // The current work-in-progress was already attempted. We need to unwind
2097- // it before we continue the normal work loop.
2098- const thrownValue = workInProgressThrownValue ;
2099- workInProgressSuspendedReason = NotSuspended ;
2100- workInProgressThrownValue = null ;
2101- if ( workInProgress !== null ) {
2102- resumeSuspendedUnitOfWork ( workInProgress , thrownValue ) ;
2103- }
2104- }
2105-
21062148 while ( workInProgress !== null && ! shouldYield ( ) ) {
21072149 // $FlowFixMe[incompatible-call] found when upgrading Flow
21082150 performUnitOfWork ( workInProgress ) ;
@@ -2137,69 +2179,15 @@ function performUnitOfWork(unitOfWork: Fiber): void {
21372179 ReactCurrentOwner . current = null ;
21382180}
21392181
2140- function resumeSuspendedUnitOfWork (
2182+ function replaySuspendedUnitOfWork (
21412183 unitOfWork : Fiber ,
21422184 thrownValue : mixed ,
21432185) : void {
2144- // This is a fork of performUnitOfWork specifcally for resuming a fiber that
2145- // just suspended. In some cases, we may choose to retry the fiber immediately
2146- // instead of unwinding the stack. It's a separate function to keep the
2147- // additional logic out of the work loop's hot path.
2148-
2149- const wasPinged =
2150- workInProgressSuspendedThenableState !== null &&
2151- isThenableStateResolved ( workInProgressSuspendedThenableState ) ;
2152-
2153- if ( ! wasPinged ) {
2154- // The thenable wasn't pinged. Return to the normal work loop. This will
2155- // unwind the stack, and potentially result in showing a fallback.
2156- workInProgressSuspendedThenableState = null ;
2157-
2158- const returnFiber = unitOfWork . return ;
2159- if ( returnFiber === null || workInProgressRoot === null ) {
2160- // Expected to be working on a non-root fiber. This is a fatal error
2161- // because there's no ancestor that can handle it; the root is
2162- // supposed to capture all errors that weren't caught by an error
2163- // boundary.
2164- workInProgressRootExitStatus = RootFatalErrored ;
2165- workInProgressRootFatalError = thrownValue ;
2166- // Set `workInProgress` to null. This represents advancing to the next
2167- // sibling, or the parent if there are no siblings. But since the root
2168- // has no siblings nor a parent, we set it to null. Usually this is
2169- // handled by `completeUnitOfWork` or `unwindWork`, but since we're
2170- // intentionally not calling those, we need set it here.
2171- // TODO: Consider calling `unwindWork` to pop the contexts.
2172- workInProgress = null ;
2173- return ;
2174- }
2175-
2176- try {
2177- // Find and mark the nearest Suspense or error boundary that can handle
2178- // this "exception".
2179- throwException (
2180- workInProgressRoot ,
2181- returnFiber ,
2182- unitOfWork ,
2183- thrownValue ,
2184- workInProgressRootRenderLanes ,
2185- ) ;
2186- } catch ( error ) {
2187- // We had trouble processing the error. An example of this happening is
2188- // when accessing the `componentDidCatch` property of an error boundary
2189- // throws an error. A weird edge case. There's a regression test for this.
2190- // To prevent an infinite loop, bubble the error up to the next parent.
2191- workInProgress = returnFiber ;
2192- throw error ;
2193- }
2194-
2195- // Return to the normal work loop.
2196- completeUnitOfWork ( unitOfWork ) ;
2197- return ;
2198- }
2199-
2200- // The work-in-progress was immediately pinged. Instead of unwinding the
2201- // stack and potentially showing a fallback, unwind only the last stack frame,
2202- // reset the fiber, and try rendering it again.
2186+ // This is a fork of performUnitOfWork specifcally for replaying a fiber that
2187+ // just suspended.
2188+ //
2189+ // Instead of unwinding the stack and potentially showing a fallback, unwind
2190+ // only the last stack frame, reset the fiber, and try rendering it again.
22032191 const current = unitOfWork . alternate ;
22042192 unwindInterruptedWork ( current , unitOfWork , workInProgressRootRenderLanes ) ;
22052193 unitOfWork = workInProgress = resetWorkInProgress ( unitOfWork , renderLanes ) ;
@@ -2232,6 +2220,55 @@ function resumeSuspendedUnitOfWork(
22322220 ReactCurrentOwner . current = null ;
22332221}
22342222
2223+ function unwindSuspendedUnitOfWork ( unitOfWork : Fiber , thrownValue : mixed ) {
2224+ // This is a fork of performUnitOfWork specifcally for unwinding a fiber
2225+ // that threw an exception.
2226+ //
2227+ // Return to the normal work loop. This will unwind the stack, and potentially
2228+ // result in showing a fallback.
2229+ workInProgressSuspendedThenableState = null ;
2230+
2231+ const returnFiber = unitOfWork . return ;
2232+ if ( returnFiber === null || workInProgressRoot === null ) {
2233+ // Expected to be working on a non-root fiber. This is a fatal error
2234+ // because there's no ancestor that can handle it; the root is
2235+ // supposed to capture all errors that weren't caught by an error
2236+ // boundary.
2237+ workInProgressRootExitStatus = RootFatalErrored ;
2238+ workInProgressRootFatalError = thrownValue ;
2239+ // Set `workInProgress` to null. This represents advancing to the next
2240+ // sibling, or the parent if there are no siblings. But since the root
2241+ // has no siblings nor a parent, we set it to null. Usually this is
2242+ // handled by `completeUnitOfWork` or `unwindWork`, but since we're
2243+ // intentionally not calling those, we need set it here.
2244+ // TODO: Consider calling `unwindWork` to pop the contexts.
2245+ workInProgress = null ;
2246+ return ;
2247+ }
2248+
2249+ try {
2250+ // Find and mark the nearest Suspense or error boundary that can handle
2251+ // this "exception".
2252+ throwException (
2253+ workInProgressRoot ,
2254+ returnFiber ,
2255+ unitOfWork ,
2256+ thrownValue ,
2257+ workInProgressRootRenderLanes ,
2258+ ) ;
2259+ } catch ( error ) {
2260+ // We had trouble processing the error. An example of this happening is
2261+ // when accessing the `componentDidCatch` property of an error boundary
2262+ // throws an error. A weird edge case. There's a regression test for this.
2263+ // To prevent an infinite loop, bubble the error up to the next parent.
2264+ workInProgress = returnFiber ;
2265+ throw error ;
2266+ }
2267+
2268+ // Return to the normal work loop.
2269+ completeUnitOfWork ( unitOfWork ) ;
2270+ }
2271+
22352272export function getSuspendedThenableState ( ) : ThenableState | null {
22362273 return workInProgressSuspendedThenableState ;
22372274}
0 commit comments