@@ -200,13 +200,14 @@ const LegacyUnbatchedContext = /* */ 0b001000;
200
200
const RenderContext = /* */ 0b010000 ;
201
201
const CommitContext = /* */ 0b100000 ;
202
202
203
- type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 ;
203
+ type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 | 6 ;
204
204
const 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 ;
210
211
211
212
export type Thenable = {
212
213
then ( resolve : ( ) => mixed , reject ?: ( ) => mixed ) : Thenable | void ,
@@ -225,6 +226,8 @@ let workInProgress: Fiber | null = null;
225
226
let renderExpirationTime : ExpirationTime = NoWork ;
226
227
// Whether to root completed, errored, suspended, etc.
227
228
let workInProgressRootExitStatus : RootExitStatus = RootIncomplete ;
229
+ // A fatal error, if one is thrown
230
+ let workInProgressRootFatalError : mixed = null ;
228
231
// Most recent event time among processed updates during this render.
229
232
// This is conceptually a time stamp but expressed in terms of an ExpirationTime
230
233
// because we deal mostly with expiration times in the hot path, so this avoids
@@ -685,28 +688,7 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
685
688
workLoopConcurrent ( ) ;
686
689
break ;
687
690
} 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 ) ;
710
692
}
711
693
} while ( true ) ;
712
694
resetContextDependencies ( ) ;
@@ -715,36 +697,42 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
715
697
if ( enableSchedulerTracing ) {
716
698
popInteractions ( ( ( prevInteractions : any ) : Set < Interaction > ) ) ;
717
699
}
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 ( ) ;
727
700
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
+ }
731
709
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
+ }
733
729
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
+ }
748
736
}
749
737
}
750
738
return null ;
@@ -760,8 +748,9 @@ function finishConcurrentRender(
760
748
workInProgressRoot = null ;
761
749
762
750
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.' ) ;
765
754
}
766
755
// Flow knows about invariant, so it complains if I add a break
767
756
// statement, but eslint doesn't know about invariant, so it complains
@@ -1042,28 +1031,7 @@ function performSyncWorkOnRoot(root) {
1042
1031
workLoopSync ( ) ;
1043
1032
break ;
1044
1033
} 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 ) ;
1067
1035
}
1068
1036
} while ( true ) ;
1069
1037
resetContextDependencies ( ) ;
@@ -1072,46 +1040,63 @@ function performSyncWorkOnRoot(root) {
1072
1040
if ( enableSchedulerTracing ) {
1073
1041
popInteractions ( ( ( prevInteractions : any ) : Set < Interaction > ) ) ;
1074
1042
}
1075
- }
1076
1043
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
+ }
1082
1052
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
+ }
1086
1070
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
+ }
1089
1076
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
+ }
1099
1079
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 ( ) ;
1107
1096
}
1108
- commitRoot ( root ) ;
1109
1097
}
1098
+ commitRoot ( root ) ;
1110
1099
}
1111
- // Before exiting, make sure there's a callback scheduled for the next
1112
- // pending level.
1113
- ensureRootIsScheduled ( root ) ;
1114
- return null ;
1115
1100
}
1116
1101
1117
1102
export function flushRoot ( root : FiberRoot , expirationTime : ExpirationTime ) {
@@ -1320,6 +1305,7 @@ function prepareFreshStack(root, expirationTime) {
1320
1305
workInProgress = createWorkInProgress ( root . current , null , expirationTime ) ;
1321
1306
renderExpirationTime = expirationTime ;
1322
1307
workInProgressRootExitStatus = RootIncomplete ;
1308
+ workInProgressRootFatalError = null ;
1323
1309
workInProgressRootLatestProcessedExpirationTime = Sync ;
1324
1310
workInProgressRootLatestSuspenseTimeout = Sync ;
1325
1311
workInProgressRootCanSuspendUsingConfig = null ;
@@ -1336,7 +1322,21 @@ function prepareFreshStack(root, expirationTime) {
1336
1322
}
1337
1323
}
1338
1324
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
+
1340
1340
if ( enableProfilerTimer && sourceFiber . mode & ProfileMode ) {
1341
1341
// Record the time spent rendering before an error was thrown. This
1342
1342
// avoids inaccurate Profiler durations in the case of a
@@ -1346,14 +1346,14 @@ function handleError(root, returnFiber, sourceFiber, thrownValue) {
1346
1346
1347
1347
throwException (
1348
1348
root ,
1349
- returnFiber ,
1349
+ workInProgress . return ,
1350
1350
sourceFiber ,
1351
1351
thrownValue ,
1352
1352
renderExpirationTime ,
1353
1353
) ;
1354
1354
// TODO: This is not wrapped in a try-catch, so if the complete phase
1355
1355
// throws, we won't capture it.
1356
- return completeUnitOfWork ( sourceFiber ) ;
1356
+ workInProgress = completeUnitOfWork ( sourceFiber ) ;
1357
1357
}
1358
1358
1359
1359
function pushDispatcher ( root ) {
0 commit comments