@@ -339,12 +339,6 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
339339 }
340340
341341 priorityContext = previousPriorityContext ;
342-
343- // Clear any errors that may have occurred during this commit
344- // TODO: Possibly recursive. Can we move this into performWork? Not sure how
345- // because we need to clear errors after every commit, and one of the call
346- // sites of commitAllWork is completeUnitOfWork.
347- errorLoop ( ) ;
348342 }
349343
350344 function resetWorkPriority ( workInProgress : Fiber ) {
@@ -521,7 +515,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
521515 performWork ( AnimationPriority ) ;
522516 }
523517
524- function errorLoop ( ) {
518+ function clearErrors ( ) {
525519 if ( ! nextUnitOfWork ) {
526520 nextUnitOfWork = findNextUnitOfWork ( ) ;
527521 }
@@ -537,156 +531,117 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
537531 nextUnitOfWork = performUnitOfWork ( nextUnitOfWork ) ;
538532 }
539533 if ( ! nextUnitOfWork ) {
534+ // If performUnitOfWork returns null, that means we just comitted
535+ // a root. Normally we'd need to clear any errors that were scheduled
536+ // during the commit phase. But we're already clearing errors, so
537+ // we can continue.
540538 nextUnitOfWork = findNextUnitOfWork ( ) ;
541539 }
542540 }
543541 }
544542
545- function workLoop ( priorityLevel ) {
546- if ( ! nextUnitOfWork ) {
547- nextUnitOfWork = findNextUnitOfWork ( ) ;
548- }
549- while ( nextUnitOfWork &&
550- nextPriorityLevel !== NoWork &&
551- nextPriorityLevel <= priorityLevel ) {
552- nextUnitOfWork = performUnitOfWork ( nextUnitOfWork ) ;
553- if ( ! nextUnitOfWork ) {
554- nextUnitOfWork = findNextUnitOfWork ( ) ;
555- }
556- }
557- }
543+ function workLoop ( priorityLevel , deadline : Deadline | null , deadlineHasExpired : boolean ) : boolean {
544+ // Clear any errors.
545+ clearErrors ( ) ;
558546
559- // Returns true if we exit because the deadline has expired
560- function deferredWorkLoop ( deadline ) : boolean {
561- let deadlineHasExpired = false ;
562547 if ( ! nextUnitOfWork ) {
563548 nextUnitOfWork = findNextUnitOfWork ( ) ;
564549 }
565- while ( nextUnitOfWork && ! deadlineHasExpired ) {
566- if ( deadline . timeRemaining ( ) > timeHeuristicForUnitOfWork ) {
567- nextUnitOfWork = performUnitOfWork ( nextUnitOfWork ) ;
568- // The order of the checks here is important: we should only check if
569- // there's a pending commit if performUnitOfWork returns null.
570- // Alternatively, to avoid checking for the pending commit, we could
571- // get it from the nextScheduledRoot. However, that requires additional
572- // checks to satisfy Flow. Since the logical operator short-circuits,
573- // this should be fine.
574- if ( ! nextUnitOfWork && pendingCommit ) {
575- // performUnitOfWork returned null and we have a pendingCommit. If we
576- // have time, we should commit the work now.
577- if ( deadline . timeRemaining ( ) > timeHeuristicForUnitOfWork ) {
578- commitAllWork ( pendingCommit ) ;
579- nextUnitOfWork = findNextUnitOfWork ( ) ;
580- } else {
581- deadlineHasExpired = true ;
550+
551+ if ( deadline ) {
552+ // The deferred work loop will run until there's no time left in
553+ // the current frame.
554+ while ( nextUnitOfWork && ! deadlineHasExpired ) {
555+ if ( deadline . timeRemaining ( ) > timeHeuristicForUnitOfWork ) {
556+ nextUnitOfWork = performUnitOfWork ( nextUnitOfWork ) ;
557+ // In a deferred work batch, iff nextUnitOfWork returns null, we just
558+ // completed a root and a pendingCommit exists. Logically, we could
559+ // omit either of the checks in the following condition, but we need
560+ // both to satisfy Flow.
561+ if ( ! nextUnitOfWork && pendingCommit ) {
562+ // If we have time, we should commit the work now.
563+ if ( deadline . timeRemaining ( ) > timeHeuristicForUnitOfWork ) {
564+ commitAllWork ( pendingCommit ) ;
565+ nextUnitOfWork = findNextUnitOfWork ( ) ;
566+ // Clear any errors that were scheduled during the commit phase.
567+ clearErrors ( ) ;
568+ } else {
569+ deadlineHasExpired = true ;
570+ }
571+ // Otherwise the root will committed in the next frame.
582572 }
583- // Otherwise the root will committed in the next frame.
573+ } else {
574+ deadlineHasExpired = true ;
575+ }
576+ }
577+ } else {
578+ // The non-deferred work loop will run until there's no more work
579+ // at the given priority level
580+ if ( priorityLevel >= HighPriority ) {
581+ throw new Error (
582+ 'Deferred work should only be performed using deferredWorkLoop'
583+ ) ;
584+ }
585+ while ( nextUnitOfWork &&
586+ nextPriorityLevel !== NoWork &&
587+ nextPriorityLevel <= priorityLevel ) {
588+ nextUnitOfWork = performUnitOfWork ( nextUnitOfWork ) ;
589+ if ( ! nextUnitOfWork ) {
590+ nextUnitOfWork = findNextUnitOfWork ( ) ;
591+ // performUnitOfWork returned null, which means we just comitted a
592+ // root. Clear any errors that were scheduled during the commit phase.
593+ clearErrors ( ) ;
584594 }
585- } else {
586- deadlineHasExpired = true ;
587595 }
588596 }
597+
589598 return deadlineHasExpired ;
590599 }
591600
592- function performWork ( priorityLevel : PriorityLevel , deadline : null | Deadline ) {
601+ function performWork ( priorityLevel : PriorityLevel , deadline : Deadline | null ) {
593602 if ( isPerformingWork ) {
594603 throw new Error ( 'performWork was called recursively.' ) ;
595604 }
596605 isPerformingWork = true ;
597606 const isPerformingDeferredWork = Boolean ( deadline ) ;
598607 let deadlineHasExpired = false ;
599608
600- // Perform work until either there's no more work at this priority level, or
601- // (in the case of deferred work) we've run out of time.
602- while ( true ) {
603- try {
604- // Functions that contain a try-catch block are not optimizable by the
605- // JS engine. The hottest code paths have been extracted to separate
606- // functions, workLoop and deferredWorkLoop, which run on every unit of
607- // work. The loop we're in now runs infrequently: to flush task work at
608- // the end of a frame, or to restart after an error.
609- while ( priorityLevel !== NoWork ) {
610- // Before starting any work, check to see if there are any pending
611- // commits from the previous frame. An exception if we're flushing
612- // Task work in a deferred batch and the pending commit does not
613- // have Task priority.
614- if ( pendingCommit ) {
615- const isFlushingTaskWorkInDeferredBatch =
616- priorityLevel === TaskPriority &&
617- isPerformingDeferredWork &&
618- pendingCommit . pendingWorkPriority !== TaskPriority ;
619- if ( ! isFlushingTaskWorkInDeferredBatch ) {
620- commitAllWork ( pendingCommit ) ;
621- }
622- }
623-
624- // Clear any errors.
625- errorLoop ( ) ;
626-
627- if ( deadline ) {
628- // The deferred work loop will run until there's no time left in
629- // the current frame.
630- if ( ! deadlineHasExpired ) {
631- deadlineHasExpired = deferredWorkLoop ( deadline ) ;
632- }
633- } else {
634- if ( priorityLevel >= HighPriority ) {
635- throw new Error (
636- 'Deferred work should only be performed using deferredWorkLoop'
637- ) ;
638- }
639- // The non-deferred work loop will run until there's no more work
640- // at the given priority level
641- workLoop ( priorityLevel ) ;
642- }
643-
644- // Stop performing work
645- priorityLevel = NoWork ;
646-
647- // If we additional work, and we're in a deferred batch, check to see
648- // if the deadline has expired. If not, we may have more time to
649- // do work.
650- if ( nextPriorityLevel !== NoWork && isPerformingDeferredWork && ! deadlineHasExpired ) {
651- priorityLevel = nextPriorityLevel ;
652- continue ;
653- }
654-
655- // There might still be work left. Depending on the priority, we should
656- // either perform it now or schedule a callback to perform it later.
657- switch ( nextPriorityLevel ) {
658- case SynchronousPriority :
659- case TaskPriority :
660- // Perform work immediately by switching the priority level
661- // and continuing the loop.
662- priorityLevel = nextPriorityLevel ;
663- break ;
664- case AnimationPriority :
665- scheduleAnimationCallback ( performAnimationWork ) ;
666- // Even though the next unit of work has animation priority, there
667- // may still be deferred work left over as well. I think this is
668- // only important for unit tests. In a real app, a deferred callback
669- // would be scheduled during the next animation frame.
670- scheduleDeferredCallback ( performDeferredWork ) ;
671- break ;
672- case HighPriority :
673- case LowPriority :
674- case OffscreenPriority :
675- scheduleDeferredCallback ( performDeferredWork ) ;
676- break ;
677- }
609+ // This outer loop exists so that we can restart the work loop after
610+ // catching an error. It also lets us flush Task work at the end of a
611+ // deferred batch.
612+ while ( priorityLevel !== NoWork ) {
613+ // Before starting any work, check to see if there are any pending
614+ // commits from the previous frame. An exception is if we're flushing
615+ // Task work in a deferred batch and the pending commit does not
616+ // have Task priority.
617+ if ( pendingCommit ) {
618+ const isFlushingTaskWorkInDeferredBatch =
619+ priorityLevel === TaskPriority &&
620+ isPerformingDeferredWork &&
621+ pendingCommit . pendingWorkPriority !== TaskPriority ;
622+ if ( ! isFlushingTaskWorkInDeferredBatch ) {
623+ commitAllWork ( pendingCommit ) ;
678624 }
625+ }
626+
627+ // Nothing in performWork should be allowed to throw. All unsafe
628+ // operations must happen within workLoop, which is extracted to a
629+ // separate function so that it can be optimized by the JS engine.
630+ try {
631+ deadlineHasExpired = workLoop ( priorityLevel , deadline , deadlineHasExpired ) ;
679632 } catch ( error ) {
680633 // We caught an error during either the begin or complete phases.
681634 const failedWork = nextUnitOfWork ;
682635
683636 // "Capture" the error by finding the nearest boundary. If there is no
684- // error boundary, the nearest host container acts as one.
637+ // error boundary, the nearest host container acts as one. If
638+ // captureError returns null, the error was intentionally ignored.
685639 const maybeBoundary = captureError ( failedWork , error ) ;
686640 if ( maybeBoundary ) {
687641 const boundary = maybeBoundary ;
688642
689- // Complete the boundary as if it rendered null.
643+ // Complete the boundary as if it rendered null. This will unmount
644+ // the failed tree.
690645 beginFailedWork ( boundary . alternate , boundary , priorityLevel ) ;
691646
692647 // The next unit of work is now the boundary that captured the error.
@@ -705,7 +660,41 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
705660 // Continue performing work
706661 continue ;
707662 }
708- break ;
663+
664+ // Stop performing work
665+ priorityLevel = NoWork ;
666+
667+ // If have we more work, and we're in a deferred batch, check to see
668+ // if the deadline has expired.
669+ if ( nextPriorityLevel !== NoWork && isPerformingDeferredWork && ! deadlineHasExpired ) {
670+ // We have more time to do work.
671+ priorityLevel = nextPriorityLevel ;
672+ continue ;
673+ }
674+
675+ // There might be work left. Depending on the priority, we should
676+ // either perform it now or schedule a callback to perform it later.
677+ switch ( nextPriorityLevel ) {
678+ case SynchronousPriority :
679+ case TaskPriority :
680+ // Perform work immediately by switching the priority level
681+ // and continuing the loop.
682+ priorityLevel = nextPriorityLevel ;
683+ break ;
684+ case AnimationPriority :
685+ scheduleAnimationCallback ( performAnimationWork ) ;
686+ // Even though the next unit of work has animation priority, there
687+ // may still be deferred work left over as well. I think this is
688+ // only important for unit tests. In a real app, a deferred callback
689+ // would be scheduled during the next animation frame.
690+ scheduleDeferredCallback ( performDeferredWork ) ;
691+ break ;
692+ case HighPriority :
693+ case LowPriority :
694+ case OffscreenPriority :
695+ scheduleDeferredCallback ( performDeferredWork ) ;
696+ break ;
697+ }
709698 }
710699
711700 // We're done performing work. Time to clean up.
0 commit comments