@@ -1936,6 +1936,42 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
1936
1936
1937
1937
do {
1938
1938
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
+ }
1939
1975
workLoopSync ( ) ;
1940
1976
break ;
1941
1977
} catch ( thrownValue ) {
@@ -1980,18 +2016,6 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
1980
2016
/** @noinline */
1981
2017
function workLoopSync ( ) {
1982
2018
// 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
-
1995
2019
while ( workInProgress !== null ) {
1996
2020
performUnitOfWork ( workInProgress ) ;
1997
2021
}
@@ -2039,6 +2063,36 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2039
2063
2040
2064
do {
2041
2065
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
+ }
2042
2096
workLoopConcurrent ( ) ;
2043
2097
break ;
2044
2098
} catch ( thrownValue ) {
@@ -2091,18 +2145,6 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2091
2145
/** @noinline */
2092
2146
function workLoopConcurrent ( ) {
2093
2147
// 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
-
2106
2148
while ( workInProgress !== null && ! shouldYield ( ) ) {
2107
2149
// $FlowFixMe[incompatible-call] found when upgrading Flow
2108
2150
performUnitOfWork ( workInProgress ) ;
@@ -2137,69 +2179,15 @@ function performUnitOfWork(unitOfWork: Fiber): void {
2137
2179
ReactCurrentOwner . current = null ;
2138
2180
}
2139
2181
2140
- function resumeSuspendedUnitOfWork (
2182
+ function replaySuspendedUnitOfWork (
2141
2183
unitOfWork : Fiber ,
2142
2184
thrownValue : mixed ,
2143
2185
) : 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.
2203
2191
const current = unitOfWork . alternate ;
2204
2192
unwindInterruptedWork ( current , unitOfWork , workInProgressRootRenderLanes ) ;
2205
2193
unitOfWork = workInProgress = resetWorkInProgress ( unitOfWork , renderLanes ) ;
@@ -2232,6 +2220,55 @@ function resumeSuspendedUnitOfWork(
2232
2220
ReactCurrentOwner . current = null ;
2233
2221
}
2234
2222
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
+
2235
2272
export function getSuspendedThenableState ( ) : ThenableState | null {
2236
2273
return workInProgressSuspendedThenableState ;
2237
2274
}
0 commit comments