@@ -200,13 +200,14 @@ const LegacyUnbatchedContext = /* */ 0b001000;
200200const RenderContext = /* */ 0b010000 ;
201201const CommitContext = /* */ 0b100000 ;
202202
203- type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 ;
203+ type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 | 6 ;
204204const RootIncomplete = 0 ;
205- const RootErrored = 1 ;
206- const RootSuspended = 2 ;
207- const RootSuspendedWithDelay = 3 ;
208- const RootCompleted = 4 ;
209- const RootLocked = 5 ;
205+ const RootFatalErrored = 1 ;
206+ const RootErrored = 2 ;
207+ const RootSuspended = 3 ;
208+ const RootSuspendedWithDelay = 4 ;
209+ const RootCompleted = 5 ;
210+ const RootLocked = 6 ;
210211
211212export type Thenable = {
212213 then ( resolve : ( ) => mixed , reject ?: ( ) => mixed ) : Thenable | void ,
@@ -225,6 +226,8 @@ let workInProgress: Fiber | null = null;
225226let renderExpirationTime : ExpirationTime = NoWork ;
226227// Whether to root completed, errored, suspended, etc.
227228let workInProgressRootExitStatus : RootExitStatus = RootIncomplete ;
229+ // A fatal error, if one is thrown
230+ let workInProgressRootFatalError : mixed = null ;
228231// Most recent event time among processed updates during this render.
229232// This is conceptually a time stamp but expressed in terms of an ExpirationTime
230233// because we deal mostly with expiration times in the hot path, so this avoids
@@ -685,28 +688,7 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
685688 workLoopConcurrent ( ) ;
686689 break ;
687690 } catch ( thrownValue ) {
688- // Reset module-level state that was set during the render phase.
689- resetContextDependencies ( ) ;
690- resetHooks ( ) ;
691-
692- if ( workInProgress === null || workInProgress . return === null ) {
693- // Expected to be working on a non-root fiber. This is a fatal error
694- // because there's no ancestor that can handle it; the root is
695- // supposed to capture all errors that weren't caught by an error
696- // boundary.
697- prepareFreshStack ( root , expirationTime ) ;
698- executionContext = prevExecutionContext ;
699- markRootSuspendedAtTime ( root , expirationTime ) ;
700- ensureRootIsScheduled ( root ) ;
701- throw thrownValue ;
702- }
703-
704- workInProgress = handleError (
705- root ,
706- workInProgress . return ,
707- workInProgress ,
708- thrownValue ,
709- ) ;
691+ handleError ( root , workInProgress , thrownValue ) ;
710692 }
711693 } while ( true ) ;
712694 resetContextDependencies ( ) ;
@@ -715,36 +697,42 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
715697 if ( enableSchedulerTracing ) {
716698 popInteractions ( ( ( prevInteractions : any ) : Set < Interaction > ) ) ;
717699 }
718- }
719-
720- if ( workInProgress !== null ) {
721- // There's still work left over. Exit without committing.
722- stopInterruptedWorkLoopTimer ( ) ;
723- } else {
724- // We now have a consistent tree. The next step is either to commit it,
725- // or, if something suspended, wait to commit it after a timeout.
726- stopFinishedWorkLoopTimer ( ) ;
727700
728- const finishedWork : Fiber = ( ( root . finishedWork =
729- root . current . alternate ) : any ) ;
730- root . finishedExpirationTime = expirationTime ;
701+ if ( workInProgressRootExitStatus === RootFatalErrored ) {
702+ const fatalError = workInProgressRootFatalError ;
703+ stopInterruptedWorkLoopTimer ( ) ;
704+ prepareFreshStack ( root , expirationTime ) ;
705+ markRootSuspendedAtTime ( root , expirationTime ) ;
706+ ensureRootIsScheduled ( root ) ;
707+ throw fatalError ;
708+ }
731709
732- resolveLocksOnRoot ( root , expirationTime ) ;
710+ if ( workInProgress !== null ) {
711+ // There's still work left over. Exit without committing.
712+ stopInterruptedWorkLoopTimer ( ) ;
713+ } else {
714+ // We now have a consistent tree. The next step is either to commit it,
715+ // or, if something suspended, wait to commit it after a timeout.
716+ stopFinishedWorkLoopTimer ( ) ;
717+
718+ const finishedWork : Fiber = ( ( root . finishedWork =
719+ root . current . alternate ) : any ) ;
720+ root . finishedExpirationTime = expirationTime ;
721+ resolveLocksOnRoot ( root , expirationTime ) ;
722+ finishConcurrentRender (
723+ root ,
724+ finishedWork ,
725+ workInProgressRootExitStatus ,
726+ expirationTime ,
727+ ) ;
728+ }
733729
734- finishConcurrentRender (
735- root ,
736- finishedWork ,
737- workInProgressRootExitStatus ,
738- expirationTime ,
739- ) ;
740- }
741- // Before exiting, make sure there's a callback scheduled for the next
742- // pending level.
743- ensureRootIsScheduled ( root ) ;
744- if ( root . callbackNode === originalCallbackNode ) {
745- // The task node scheduled for this root is the same one that's
746- // currently executed. Need to return a continuation.
747- return performConcurrentWorkOnRoot . bind ( null , root ) ;
730+ ensureRootIsScheduled ( root ) ;
731+ if ( root . callbackNode === originalCallbackNode ) {
732+ // The task node scheduled for this root is the same one that's
733+ // currently executed. Need to return a continuation.
734+ return performConcurrentWorkOnRoot . bind ( null , root ) ;
735+ }
748736 }
749737 }
750738 return null ;
@@ -760,8 +748,9 @@ function finishConcurrentRender(
760748 workInProgressRoot = null ;
761749
762750 switch ( exitStatus ) {
763- case RootIncomplete : {
764- invariant ( false , 'Should have a work-in-progress.' ) ;
751+ case RootIncomplete :
752+ case RootFatalErrored : {
753+ invariant ( false , 'Root did not complete. This is a bug in React.' ) ;
765754 }
766755 // Flow knows about invariant, so it complains if I add a break
767756 // statement, but eslint doesn't know about invariant, so it complains
@@ -1042,28 +1031,7 @@ function performSyncWorkOnRoot(root) {
10421031 workLoopSync ( ) ;
10431032 break ;
10441033 } catch ( thrownValue ) {
1045- // Reset module-level state that was set during the render phase.
1046- resetContextDependencies ( ) ;
1047- resetHooks ( ) ;
1048-
1049- if ( workInProgress === null || workInProgress . return === null ) {
1050- // Expected to be working on a non-root fiber. This is a fatal error
1051- // because there's no ancestor that can handle it; the root is
1052- // supposed to capture all errors that weren't caught by an error
1053- // boundary.
1054- prepareFreshStack ( root , expirationTime ) ;
1055- executionContext = prevExecutionContext ;
1056- markRootSuspendedAtTime ( root , expirationTime ) ;
1057- ensureRootIsScheduled ( root ) ;
1058- throw thrownValue ;
1059- }
1060-
1061- workInProgress = handleError (
1062- root ,
1063- workInProgress . return ,
1064- workInProgress ,
1065- thrownValue ,
1066- ) ;
1034+ handleError ( root , workInProgress , thrownValue ) ;
10671035 }
10681036 } while ( true ) ;
10691037 resetContextDependencies ( ) ;
@@ -1072,46 +1040,63 @@ function performSyncWorkOnRoot(root) {
10721040 if ( enableSchedulerTracing ) {
10731041 popInteractions ( ( ( prevInteractions : any ) : Set < Interaction > ) ) ;
10741042 }
1075- }
10761043
1077- invariant (
1078- workInProgressRootExitStatus !== RootIncomplete ,
1079- 'Cannot commit an incomplete root. This error is likely caused by a ' +
1080- 'bug in React. Please file an issue.' ,
1081- ) ;
1044+ if ( workInProgressRootExitStatus === RootFatalErrored ) {
1045+ const fatalError = workInProgressRootFatalError ;
1046+ stopInterruptedWorkLoopTimer ( ) ;
1047+ prepareFreshStack ( root , expirationTime ) ;
1048+ markRootSuspendedAtTime ( root , expirationTime ) ;
1049+ ensureRootIsScheduled ( root ) ;
1050+ throw fatalError ;
1051+ }
10821052
1083- // We now have a consistent tree. The next step is either to commit it,
1084- // or, if something suspended, wait to commit it after a timeout.
1085- stopFinishedWorkLoopTimer ( ) ;
1053+ if ( workInProgress !== null ) {
1054+ // This is a sync render, so we should have finished the whole tree.
1055+ invariant (
1056+ false ,
1057+ 'Cannot commit an incomplete root. This error is likely caused by a ' +
1058+ 'bug in React. Please file an issue.' ,
1059+ ) ;
1060+ } else {
1061+ // We now have a consistent tree. Because this is a sync render, we
1062+ // will commit it even if something suspended. The only exception is
1063+ // if the root is locked (using the unstable_createBatch API).
1064+ stopFinishedWorkLoopTimer ( ) ;
1065+ root . finishedWork = ( root . current . alternate : any ) ;
1066+ root . finishedExpirationTime = expirationTime ;
1067+ resolveLocksOnRoot ( root , expirationTime ) ;
1068+ finishSyncRender ( root , workInProgressRootExitStatus , expirationTime ) ;
1069+ }
10861070
1087- root . finishedWork = ( ( root . current . alternate : any ) : Fiber ) ;
1088- root . finishedExpirationTime = expirationTime ;
1071+ // Before exiting, make sure there's a callback scheduled for the next
1072+ // pending level.
1073+ ensureRootIsScheduled ( root ) ;
1074+ }
1075+ }
10891076
1090- resolveLocksOnRoot ( root , expirationTime ) ;
1091- if ( workInProgressRootExitStatus === RootLocked ) {
1092- // This root has a lock that prevents it from committing. Exit. If we
1093- // begin work on the root again, without any intervening updates, it
1094- // will finish without doing additional work.
1095- markRootSuspendedAtTime ( root , expirationTime ) ;
1096- } else {
1097- // Set this to null to indicate there's no in-progress render.
1098- workInProgressRoot = null ;
1077+ return null ;
1078+ }
10991079
1100- if ( __DEV__ ) {
1101- if (
1102- workInProgressRootExitStatus === RootSuspended ||
1103- workInProgressRootExitStatus === RootSuspendedWithDelay
1104- ) {
1105- flushSuspensePriorityWarningInDEV ( ) ;
1106- }
1080+ function finishSyncRender ( root , exitStatus , expirationTime ) {
1081+ if ( exitStatus === RootLocked ) {
1082+ // This root has a lock that prevents it from committing. Exit. If we
1083+ // begin work on the root again, without any intervening updates, it
1084+ // will finish without doing additional work.
1085+ markRootSuspendedAtTime ( root , expirationTime ) ;
1086+ } else {
1087+ // Set this to null to indicate there's no in-progress render.
1088+ workInProgressRoot = null ;
1089+
1090+ if ( __DEV__ ) {
1091+ if (
1092+ exitStatus === RootSuspended ||
1093+ exitStatus === RootSuspendedWithDelay
1094+ ) {
1095+ flushSuspensePriorityWarningInDEV ( ) ;
11071096 }
1108- commitRoot ( root ) ;
11091097 }
1098+ commitRoot ( root ) ;
11101099 }
1111- // Before exiting, make sure there's a callback scheduled for the next
1112- // pending level.
1113- ensureRootIsScheduled ( root ) ;
1114- return null ;
11151100}
11161101
11171102export function flushRoot ( root : FiberRoot , expirationTime : ExpirationTime ) {
@@ -1320,6 +1305,7 @@ function prepareFreshStack(root, expirationTime) {
13201305 workInProgress = createWorkInProgress ( root . current , null , expirationTime ) ;
13211306 renderExpirationTime = expirationTime ;
13221307 workInProgressRootExitStatus = RootIncomplete ;
1308+ workInProgressRootFatalError = null ;
13231309 workInProgressRootLatestProcessedExpirationTime = Sync ;
13241310 workInProgressRootLatestSuspenseTimeout = Sync ;
13251311 workInProgressRootCanSuspendUsingConfig = null ;
@@ -1336,7 +1322,21 @@ function prepareFreshStack(root, expirationTime) {
13361322 }
13371323}
13381324
1339- function handleError ( root , returnFiber , sourceFiber , thrownValue ) {
1325+ function handleError ( root , sourceFiber , thrownValue ) {
1326+ // Reset module-level state that was set during the render phase.
1327+ resetContextDependencies ( ) ;
1328+ resetHooks ( ) ;
1329+
1330+ if ( workInProgress === null || workInProgress . return === null ) {
1331+ // Expected to be working on a non-root fiber. This is a fatal error
1332+ // because there's no ancestor that can handle it; the root is
1333+ // supposed to capture all errors that weren't caught by an error
1334+ // boundary.
1335+ workInProgressRootExitStatus = RootFatalErrored ;
1336+ workInProgressRootFatalError = thrownValue ;
1337+ return null ;
1338+ }
1339+
13401340 if ( enableProfilerTimer && sourceFiber . mode & ProfileMode ) {
13411341 // Record the time spent rendering before an error was thrown. This
13421342 // avoids inaccurate Profiler durations in the case of a
@@ -1346,14 +1346,14 @@ function handleError(root, returnFiber, sourceFiber, thrownValue) {
13461346
13471347 throwException (
13481348 root ,
1349- returnFiber ,
1349+ workInProgress . return ,
13501350 sourceFiber ,
13511351 thrownValue ,
13521352 renderExpirationTime ,
13531353 ) ;
13541354 // TODO: This is not wrapped in a try-catch, so if the complete phase
13551355 // throws, we won't capture it.
1356- return completeUnitOfWork ( sourceFiber ) ;
1356+ workInProgress = completeUnitOfWork ( sourceFiber ) ;
13571357}
13581358
13591359function pushDispatcher ( root ) {
0 commit comments