1212
1313'use strict' ;
1414
15- import type { TrappedError } from 'ReactFiberErrorBoundary' ;
1615import type { Fiber } from 'ReactFiber' ;
1716import type { FiberRoot } from 'ReactFiberRoot' ;
1817import type { HostConfig , Deadline } from 'ReactFiberReconciler' ;
@@ -24,7 +23,6 @@ var ReactFiberCommitWork = require('ReactFiberCommitWork');
2423var ReactCurrentOwner = require ( 'ReactCurrentOwner' ) ;
2524
2625var { cloneFiber } = require ( 'ReactFiber' ) ;
27- var { trapError, acknowledgeErrorInBoundary } = require ( 'ReactFiberErrorBoundary' ) ;
2826
2927var {
3028 NoWork,
5149
5250var {
5351 HostContainer,
52+ ClassComponent,
5453} = require ( 'ReactTypeOfWork' ) ;
5554
5655if ( __DEV__ ) {
@@ -89,6 +88,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
8988 let isAnimationCallbackScheduled : boolean = false ;
9089 let isDeferredCallbackScheduled : boolean = false ;
9190
91+ let nextTrappedErrors : Array < TrappedError > | null = null ;
92+ let isHandlingErrors : boolean = false ;
93+
9294 function scheduleAnimationCallback ( callback ) {
9395 if ( ! isAnimationCallbackScheduled ) {
9496 isAnimationCallbackScheduled = true ;
@@ -246,10 +248,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
246248 }
247249 }
248250
249- // Now that the tree has been committed, we can handle errors.
250- if ( allTrappedErrors ) {
251- handleErrors ( allTrappedErrors ) ;
252- }
251+ nextTrappedErrors = nextTrappedErrors ?
252+ nextTrappedErrors . push . apply ( nextTrappedErrors , allTrappedErrors ) :
253+ allTrappedErrors ;
254+
253255 performTaskWork ( ) ;
254256 }
255257
@@ -512,13 +514,13 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
512514 switch ( priorityLevel ) {
513515 case SynchronousPriority :
514516 performSynchronousWorkUnsafe ( ) ;
515- return ;
517+ break ;
516518 case TaskPriority :
517519 performTaskWorkUnsafe ( false ) ;
518- return ;
520+ break ;
519521 case AnimationPriority :
520522 performAnimationWorkUnsafe ( ) ;
521- return ;
523+ break ;
522524 case HighPriority :
523525 case LowPriority :
524526 case OffscreenPriority :
@@ -527,9 +529,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
527529 } else {
528530 performDeferredWorkUnsafe ( deadline ) ;
529531 }
530- return ;
532+ break ;
531533 default :
532- return ;
534+ break ;
533535 }
534536 } catch ( error ) {
535537 const failedUnitOfWork = nextUnitOfWork ;
@@ -541,79 +543,143 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
541543 throw error ;
542544 }
543545 const trappedError = trapError ( failedUnitOfWork , error ) ;
544- handleErrors ( [ trappedError ] ) ;
546+ if ( nextTrappedErrors ) {
547+ nextTrappedErrors . push ( trappedError ) ;
548+ } else {
549+ nextTrappedErrors = [ trappedError ] ;
550+ }
551+ }
552+
553+ // If there were any errors, handle them now
554+ if ( nextTrappedErrors ) {
555+ handleErrors ( ) ;
545556 }
546557 }
547558
548- function handleErrors ( initialTrappedErrors : Array < TrappedError > ) : void {
549- let nextTrappedErrors = initialTrappedErrors ;
559+ // Boundaries that have been acknowledged
560+ let knownBoundaries : Set < Fiber | null > = new Set ( ) ;
561+
562+ function handleErrors ( ) : void {
563+ // Prevent recursion
564+ if ( isHandlingErrors ) {
565+ return ;
566+ }
567+ isHandlingErrors = true ;
568+
550569 let firstUncaughtError = null ;
551570
571+ // All work created by error boundaries should have Task priority
572+ // so that it finishes before this function exits
552573 const previousPriorityContext = priorityContext ;
553574 priorityContext = TaskPriority ;
554575
555- // In each phase, we will attempt to pass errors to boundaries and re-render them.
556- // If we get more errors, we propagate them to higher boundaries in the next iterations.
557-
558- while ( nextTrappedErrors ) {
559- const trappedErrors = nextTrappedErrors ;
560- nextTrappedErrors = null ;
561-
562- // Pass errors to all affected boundaries.
563- const affectedBoundaries : Set < Fiber > = new Set ( ) ;
564- trappedErrors . forEach ( trappedError => {
576+ // Keep looping until there are no more trapped errors
577+ while ( true ) {
578+ while ( nextTrappedErrors && nextTrappedErrors . length ) {
579+ const trappedError = nextTrappedErrors . shift ( ) ;
565580 const boundary = trappedError . boundary ;
566581 const error = trappedError . error ;
567582 if ( ! boundary ) {
568583 firstUncaughtError = firstUncaughtError || error ;
569- return ;
584+ continue ;
570585 }
571586 // Don't visit boundaries twice.
572- if ( affectedBoundaries . has ( boundary ) ) {
573- return ;
587+ if ( knownBoundaries . has ( boundary ) ) {
588+ continue ;
574589 }
575- // Give error boundary a chance to update its state.
590+
576591 try {
592+ knownBoundaries . add ( boundary ) ;
593+ // Give error boundary a chance to update its state.
594+ // Updates will be scheduled with Task priority.
577595 acknowledgeErrorInBoundary ( boundary , error ) ;
578- affectedBoundaries . add ( boundary ) ;
579596 } catch ( nextError ) {
580- // If it throws, propagate the error.
581- nextTrappedErrors = nextTrappedErrors || [ ] ;
582- nextTrappedErrors . push ( trapError ( boundary , nextError ) ) ;
597+ // If an error is thrown, propagate the error to the next boundary
598+ const te = trapError ( boundary , nextError ) ;
599+ if ( te . boundary ) {
600+ // If a boundary is found, push trapped error onto array
601+ nextTrappedErrors . push ( te ) ;
602+ } else {
603+ // Otherwise, we'll need to throw
604+ firstUncaughtError = firstUncaughtError || te . error ;
605+ }
583606 }
584- } ) ;
607+ }
585608
586- // We will process an update caused by each error boundary synchronously.
587- affectedBoundaries . forEach ( boundary => {
588- scheduleUpdate ( boundary ) ;
589- try {
590- nextUnitOfWork = findNextUnitOfWork ( ) ;
591- performTaskWorkUnsafe ( true ) ;
592- } catch ( nextError ) {
593- // If it throws, propagate the error.
594- nextTrappedErrors = nextTrappedErrors || [ ] ;
595- nextTrappedErrors . push ( trapError ( boundary , nextError ) ) ;
609+
610+ // Now that we attempt to flush any work that was scheduled by the boundaries
611+ // If this creates errors, they will be pushed to nextTrappedErrors and the loop will continue
612+ try {
613+ performTaskWorkUnsafe ( true ) ;
614+ } catch ( error ) {
615+ const failedUnitOfWork = nextUnitOfWork ;
616+ // Reset because it points to the error boundary:
617+ nextUnitOfWork = null ;
618+ if ( ! failedUnitOfWork ) {
619+ // We shouldn't end up here because nextUnitOfWork
620+ // should always be set while work is being performed.
621+ throw error ;
596622 }
597- } ) ;
598- }
623+ const trappedError = trapError ( failedUnitOfWork , error ) ;
624+ if ( nextTrappedErrors ) {
625+ nextTrappedErrors . push ( trappedError ) ;
626+ } else {
627+ nextTrappedErrors = [ trappedError ] ;
628+ }
629+ }
599630
600- ReactCurrentOwner . current = null ;
631+ if ( ! nextTrappedErrors || firstUncaughtError ) {
632+ break ;
633+ }
634+ }
601635
636+ nextTrappedErrors = null ;
637+ knownBoundaries = new Set ( ) ;
602638 priorityContext = previousPriorityContext ;
639+ isHandlingErrors = false ;
603640
604- // Surface the first error uncaught by the boundaries to the user.
605641 if ( firstUncaughtError ) {
606642 // We need to make sure any future root can get scheduled despite these errors.
607643 // Currently after throwing, nothing gets scheduled because these fields are set.
608644 // FIXME: this is likely a wrong fix! It's still better than ignoring updates though.
609645 nextScheduledRoot = null ;
610646 lastScheduledRoot = null ;
611-
612- // Throw any unhandled errors.
613647 throw firstUncaughtError ;
614648 }
615649 }
616650
651+ type TrappedError = {
652+ boundary : Fiber | null ,
653+ error : any ,
654+ } ;
655+
656+ function findClosestErrorBoundary ( fiber : Fiber ) : Fiber | null {
657+ let maybeErrorBoundary = fiber . return ;
658+ while ( maybeErrorBoundary ) {
659+ if ( maybeErrorBoundary . tag === ClassComponent ) {
660+ const instance = maybeErrorBoundary . stateNode ;
661+ if ( typeof instance . unstable_handleError === 'function' &&
662+ ! knownBoundaries . has ( maybeErrorBoundary ) ) {
663+ return maybeErrorBoundary ;
664+ }
665+ }
666+ maybeErrorBoundary = maybeErrorBoundary . return ;
667+ }
668+ return null ;
669+ }
670+
671+ function trapError ( fiber : Fiber , error : any ) : TrappedError {
672+ return {
673+ boundary : findClosestErrorBoundary ( fiber ) ,
674+ error,
675+ } ;
676+ }
677+
678+ function acknowledgeErrorInBoundary ( boundary : Fiber , error : any ) {
679+ const instance = boundary . stateNode ;
680+ instance . unstable_handleError ( error ) ;
681+ }
682+
617683 function scheduleWork ( root : FiberRoot ) {
618684 let priorityLevel = priorityContext ;
619685
0 commit comments