@samitouri / QOS-React-2 / commits / d4dd3408bd

Remove tryComponentDidMount/Update in favor of commit phase try block

We still need tryComponentWillUnmount because deletion should be non- interruptible. Refactoring the commit phase to better handle errors also allows us to switch between fast and slow versions of the work loop. The slower version runs whenever there are captured errors; it must check each unit of work to see if it has failed. The faster version runs in the normal case where there are no errors. Whenever an error is thrown, we switch from the fast work loop to the slow work loop.

Andrew Clark committed Dec 2, 2016 at 18:13 UTC d4dd3408bd98dfc67172412f5de6d5cec9a6bc2e
4 files changed +228 -188
src/renderers/shared/fiber/ReactFiberCommitWork.js
+43 -51
@@ -35,7 +35,7 @@ var {
35
36 module.exports = function<T, P, I, TI, C>(
37 config : HostConfig<T, P, I, TI, C>,
38 - captureError : (failedFiber : Fiber, error: Error, isUnmounting : boolean) => Fiber | null
38 + captureError : (failedFiber : Fiber, error: Error) => Fiber | null
39 ) {
40
41 const commitUpdate = config.commitUpdate;
@@ -204,15 +204,17 @@ module.exports = function<T, P, I, TI, C>(
204 }
205 }
206
207 - function commitNestedUnmounts(root : Fiber): void {
207 + function commitNestedUnmounts(root : Fiber): boolean {
208 // While we're inside a removed host node we don't want to call
209 // removeChild on the inner nodes because they're removed by the top
210 // call anyway. We also want to call componentWillUnmount on all
211 // composites before this host node is removed from the tree. Therefore
212 // we do an inner loop while we're still inside the host node.
213 + let noErrors = true;
214 let node : Fiber = root;
215 while (true) {
215 - commitUnmount(node);
216 + const noError = commitUnmount(node);
217 + noErrors = noErrors && noError;
218 if (node.child) {
219 // TODO: Coroutines need to visit the stateNode.
220 node.child.return = node;
@@ -220,26 +222,32 @@ module.exports = function<T, P, I, TI, C>(
222 continue;
223 }
224 if (node === root) {
223 - return;
225 + return noErrors;
226 }
227 while (!node.sibling) {
228 if (!node.return || node.return === root) {
227 - return;
229 + return noErrors;
230 }
231 node = node.return;
232 }
233 node.sibling.return = node.return;
234 node = node.sibling;
235 }
236 + // This is unreachable but without it Flow complains about implicitly-
237 + // returned undefined.
238 + return noErrors; // eslint-disable-line no-unreachable
239 }
240
236 - function unmountHostComponents(parent, current): void {
241 + // Returns true if it completed without any errors
242 + function unmountHostComponents(parent, current): boolean {
243 // We only have the top Fiber that was inserted but we need recurse down its
244 // children to find all the terminal nodes.
245 + let noErrors = true;
246 let node : Fiber = current;
247 while (true) {
248 if (node.tag === HostComponent || node.tag === HostText) {
242 - commitNestedUnmounts(node);
249 + const noError = commitNestedUnmounts(node);
250 + noErrors = noErrors && noError;
251 // After all the children have unmounted, it is now safe to remove the
252 // node from the tree.
253 removeChild(parent, node.stateNode);
@@ -252,7 +260,8 @@ module.exports = function<T, P, I, TI, C>(
260 continue;
261 }
262 } else {
255 - commitUnmount(node);
263 + const noError = commitUnmount(node);
264 + noErrors = noErrors && noError;
265 if (node.child) {
266 // TODO: Coroutines need to visit the stateNode.
267 node.child.return = node;
@@ -261,11 +270,11 @@ module.exports = function<T, P, I, TI, C>(
270 }
271 }
272 if (node === current) {
264 - return;
273 + return noErrors;
274 }
275 while (!node.sibling) {
276 if (!node.return || node.return === current) {
268 - return;
277 + return noErrors;
278 }
279 node = node.return;
280 if (node.tag === HostPortal) {
@@ -277,13 +286,16 @@ module.exports = function<T, P, I, TI, C>(
286 node.sibling.return = node.return;
287 node = node.sibling;
288 }
289 + // This is unreachable but without it Flow complains about implicitly-
290 + // returned undefined.
291 + return noErrors; // eslint-disable-line no-unreachable
292 }
293
282 - function commitDeletion(current : Fiber) : void {
294 + function commitDeletion(current : Fiber) : boolean {
295 // Recursively delete all host nodes from the parent.
296 const parent = getHostParent(current);
297 // Detach refs and call componentWillUnmount() on the whole subtree.
286 - unmountHostComponents(parent, current);
298 + const noErrors = unmountHostComponents(parent, current);
299
300 // Cut off the return pointers to disconnect it from the tree. Ideally, we
301 // should clear the child pointer of the parent alternate to let this
@@ -296,9 +308,12 @@ module.exports = function<T, P, I, TI, C>(
308 current.alternate.child = null;
309 current.alternate.return = null;
310 }
311 + return noErrors;
312 }
313
301 - function commitUnmount(current : Fiber) : void {
314 + // Returns true if it completed without any errors
315 + function commitUnmount(current : Fiber) : boolean {
316 + let noErrors = true;
317 switch (current.tag) {
318 case ClassComponent: {
319 detachRef(current);
@@ -306,25 +321,28 @@ module.exports = function<T, P, I, TI, C>(
321 if (typeof instance.componentWillUnmount === 'function') {
322 const error = tryCallComponentWillUnmount(instance);
323 if (error) {
309 - captureError(current, error, true);
324 + captureError(current, error);
325 + noErrors = false;
326 }
327 }
312 - return;
328 + break;
329 }
330 case HostComponent: {
331 detachRef(current);
316 - return;
332 + break;
333 }
334 case CoroutineComponent: {
319 - commitNestedUnmounts(current.stateNode);
320 - return;
335 + const noError = commitNestedUnmounts(current.stateNode);
336 + noErrors = noErrors && noError;
337 + break;
338 }
339 case HostPortal: {
340 // TODO: this is recursive.
341 commitDeletion(current);
325 - return;
342 + break;
343 }
344 }
345 + return noErrors;
346 }
347
348 function commitWork(current : ?Fiber, finishedWork : Fiber) : void {
@@ -369,17 +387,16 @@ module.exports = function<T, P, I, TI, C>(
387 switch (finishedWork.tag) {
388 case ClassComponent: {
389 const instance = finishedWork.stateNode;
372 - let firstError = null;
390 if (finishedWork.effectTag & Update) {
391 if (!current) {
392 if (typeof instance.componentDidMount === 'function') {
376 - firstError = tryCallComponentDidMount(instance);
393 + instance.componentDidMount();
394 }
395 } else {
396 if (typeof instance.componentDidUpdate === 'function') {
397 const prevProps = current.memoizedProps;
398 const prevState = current.memoizedState;
382 - firstError = tryCallComponentDidUpdate(instance, prevProps, prevState);
399 + instance.componentDidUpdate(prevProps, prevState);
400 }
401 }
402 attachRef(current, finishedWork, instance);
@@ -390,26 +407,19 @@ module.exports = function<T, P, I, TI, C>(
407 }
408 if (finishedWork.effectTag & Callback) {
409 if (finishedWork.callbackList) {
393 - const callbackError = callCallbacks(finishedWork.callbackList, instance);
394 - firstError = firstError || callbackError;
410 + const callbackList = finishedWork.callbackList;
411 finishedWork.callbackList = null;
412 + callCallbacks(callbackList, instance);
413 }
414 }
398 - if (firstError) {
399 - captureError(finishedWork, firstError, false);
400 - }
415 return;
416 }
417 case HostRoot: {
418 const rootFiber = finishedWork.stateNode;
405 - let firstError = null;
419 if (rootFiber.callbackList) {
407 - const { callbackList } = rootFiber;
420 + const callbackList = rootFiber.callbackList;
421 rootFiber.callbackList = null;
409 - firstError = callCallbacks(callbackList, rootFiber.current.child.stateNode);
410 - }
411 - if (firstError) {
412 - captureError(rootFiber, firstError, false);
422 + callCallbacks(callbackList, rootFiber.current.child.stateNode);
423 }
424 return;
425 }
@@ -431,24 +441,6 @@ module.exports = function<T, P, I, TI, C>(
441 }
442 }
443
434 - function tryCallComponentDidMount(instance) {
435 - try {
436 - instance.componentDidMount();
437 - return null;
438 - } catch (error) {
439 - return error;
440 - }
441 - }
442 -
443 - function tryCallComponentDidUpdate(instance, prevProps, prevState) {
444 - try {
445 - instance.componentDidUpdate(prevProps, prevState);
446 - return null;
447 - } catch (error) {
448 - return error;
449 - }
450 - }
451 -
444 function tryCallComponentWillUnmount(instance) {
445 try {
446 instance.componentWillUnmount();
src/renderers/shared/fiber/ReactFiberScheduler.js
+179 -124
@@ -93,13 +93,15 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
93 let nextScheduledRoot : ?FiberRoot = null;
94 let lastScheduledRoot : ?FiberRoot = null;
95
96 - // Keep track of which host environment callbacks are scheduled
96 + // Keep track of which host environment callbacks are scheduled.
97 let isAnimationCallbackScheduled : boolean = false;
98 let isDeferredCallbackScheduled : boolean = false;
99
100 let capturedErrors : Map<Fiber, Error> | null = null;
101 let firstUncaughtError : Error | null = null;
102
103 + let isUnmounting : boolean = false;
104 +
105 function scheduleAnimationCallback(callback) {
106 if (!isAnimationCallbackScheduled) {
107 isAnimationCallbackScheduled = true;
@@ -116,7 +118,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
118
119 function findNextUnitOfWork() {
120 if (pendingCommit) {
119 - return null;
121 + return pendingCommit;
122 }
123
124 // Clear out roots with no more work on them, or if they have uncaught errors
@@ -163,7 +165,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
165 return null;
166 }
167
166 - function commitAllWork(finishedWork : Fiber) {
168 + // Returns true if the commit phase finishes without any errors.
169 + function commitAllWork(finishedWork : Fiber): boolean {
170 + let noErrors = true;
171 +
172 pendingCommit = null;
173 const root : FiberRoot = (finishedWork.stateNode : any);
174 resetWorkPriority(finishedWork);
@@ -182,6 +187,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
187 // ref unmounts.
188 let effectfulFiber = finishedWork.firstEffect;
189 pass1: while (true) {
190 + let didHandleRoot = false;
191 try {
192 while (effectfulFiber) {
193 if (effectfulFiber.effectTag & ContentReset) {
@@ -222,29 +228,36 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
228 break;
229 }
230 case Deletion: {
225 - commitDeletion(effectfulFiber);
231 + isUnmounting = true;
232 + const noError = commitDeletion(effectfulFiber);
233 + noErrors = noErrors && noError;
234 + isUnmounting = false;
235 break;
236 }
237 }
238 effectfulFiber = effectfulFiber.nextEffect;
239 }
240 +
241 + // Finally if the root itself had an effect, we perform that since it is
242 + // not part of the effect list.
243 + if (!didHandleRoot && finishedWork.effectTag !== NoEffect) {
244 + didHandleRoot = true;
245 + const current = finishedWork.alternate;
246 + commitWork(current, finishedWork);
247 + }
248 } catch (error) {
249 + noErrors = false;
250 + captureError(effectfulFiber || null, error);
251 if (effectfulFiber) {
233 - captureError(effectfulFiber, error, false);
252 effectfulFiber = effectfulFiber.nextEffect;
235 - continue pass1;
253 }
254 + // Clean-up
255 + isUnmounting = false;
256 + continue pass1;
257 }
258 break;
259 }
260
241 - // Finally if the root itself had an effect, we perform that since it is
242 - // not part of the effect list.
243 - if (finishedWork.effectTag !== NoEffect) {
244 - const current = finishedWork.alternate;
245 - commitWork(current, finishedWork);
246 - }
247 -
261 resetAfterCommit();
262
263 // Next, we'll perform all life-cycles and ref callbacks. Life-cycles
@@ -254,6 +267,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
267 const previousPriorityContext = priorityContext;
268 priorityContext = TaskPriority;
269 pass2: while (true) {
270 + let didHandleRoot = false;
271 try {
272 while (effectfulFiber) {
273 const current = effectfulFiber.alternate;
@@ -276,28 +290,31 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
290 // tags to reason about the current life-cycle.
291 effectfulFiber = next;
292 }
293 +
294 + // Finally if the root itself had an effect, we perform that since it is
295 + // not part of the effect list.
296 + if (!didHandleRoot && finishedWork.effectTag !== NoEffect) {
297 + didHandleRoot = true;
298 + const current = finishedWork.alternate;
299 + commitLifeCycles(current, finishedWork);
300 + if (finishedWork.effectTag & Err) {
301 + commitErrorHandling(finishedWork);
302 + }
303 + }
304 } catch (error) {
305 + noErrors = false;
306 + captureError(effectfulFiber || null, error);
307 if (effectfulFiber) {
281 - captureError(effectfulFiber, error, false);
308 const next = effectfulFiber.nextEffect;
309 effectfulFiber.nextEffect = null;
310 effectfulFiber = next;
285 - continue pass2;
311 }
312 + continue pass2;
313 }
314 priorityContext = previousPriorityContext;
315 break;
316 }
317
292 - // Lifecycles on the root itself
293 - if (finishedWork.effectTag !== NoEffect) {
294 - const current = finishedWork.alternate;
295 - commitLifeCycles(current, finishedWork);
296 - if (finishedWork.effectTag & Err) {
297 - commitErrorHandling(finishedWork);
298 - }
299 - }
300 -
318 if (capturedErrors) {
319 if (capturedErrors.size) {
320 capturedErrors.forEach((error, boundary) => {
@@ -308,7 +325,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
325 }
326 }
327
311 - return null;
328 + nextUnitOfWork = null;
329 +
330 + return noErrors;
331 }
332
333 function resetWorkPriority(workInProgress : Fiber) {
@@ -492,63 +511,113 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
511 }
512
513 function workLoop(priorityLevel) {
495 - // If there are errors, use a forked version of performUnitOfWork
496 - // TODO: Need to make commitAllWork throw on error before doing this
497 - if (true || (capturedErrors && capturedErrors.size)) {
514 + outer: while (true) {
515 + if (!nextUnitOfWork) {
516 + nextUnitOfWork = findNextUnitOfWork();
517 + }
518 +
519 + // If there are errors, use a forked version of performUnitOfWork
520 + // TODO: Need to make commitAllWork throw on error before doing this
521 + if (capturedErrors && capturedErrors.size) {
522 + while (true) {
523 + if (!nextUnitOfWork && !pendingCommit) {
524 + nextUnitOfWork = findNextUnitOfWork();
525 + }
526 + // If we're currently flushing Task work, but the pending commit does
527 + // not have Task priority, don't commit it — we're at the end of a
528 + // deferred work batch and the commit should happen in the next batch.
529 + if (
530 + pendingCommit &&
531 + !(priorityLevel === TaskPriority && pendingCommit.pendingWorkPriority !== TaskPriority)
532 + ) {
533 + commitAllWork(pendingCommit);
534 + } else if (nextUnitOfWork && nextPriorityLevel !== NoWork && nextPriorityLevel <= priorityLevel) {
535 + if (capturedErrors && capturedErrors.has(nextUnitOfWork)) {
536 + nextUnitOfWork = performFailedUnitOfWork(nextUnitOfWork);
537 + } else {
538 + nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
539 + }
540 + } else {
541 + return;
542 + }
543 + }
544 + }
545 +
546 + // Otherwise, handle the normal, faster case where there are no errors:
547 while (true) {
548 if (!nextUnitOfWork && !pendingCommit) {
549 nextUnitOfWork = findNextUnitOfWork();
550 }
502 - if (pendingCommit && !(priorityLevel === TaskPriority && pendingCommit.pendingWorkPriority !== TaskPriority)) {
503 - nextUnitOfWork = commitAllWork(pendingCommit);
504 - } else if (nextUnitOfWork && nextPriorityLevel !== NoWork && nextPriorityLevel <= priorityLevel) {
505 - if (capturedErrors && capturedErrors.has(nextUnitOfWork)) {
506 - nextUnitOfWork = performFailedUnitOfWork(nextUnitOfWork);
507 - } else {
508 - nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
551 + // If we're currently flushing Task work, but the pending commit does
552 + // not have Task priority, don't commit it — we're at the end of a
553 + // deferred work batch and the commit should happen in the next batch.
554 + if (
555 + pendingCommit &&
556 + !(priorityLevel === TaskPriority && pendingCommit.pendingWorkPriority !== TaskPriority)
557 + ) {
558 + const noError = commitAllWork(pendingCommit);
559 + if (!noError) {
560 + // There was an error during the commit phase. Restart the outer
561 + // loop so we can switch to the slower version of the work loop that
562 + // checks for failed work.
563 + continue outer;
564 }
565 + } else if (nextUnitOfWork && nextPriorityLevel !== NoWork && nextPriorityLevel <= priorityLevel) {
566 + nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
567 } else {
568 return;
569 }
570 }
571 }
572 + }
573
516 - // Otherwise, handle the normal, faster case where there are no errors:
517 - while (true) {
518 - if (!nextUnitOfWork && !pendingCommit) {
574 + function deferredWorkLoop(deadline) {
575 + outer: while (true) {
576 + if (!nextUnitOfWork) {
577 nextUnitOfWork = findNextUnitOfWork();
578 }
521 - if (pendingCommit && !(priorityLevel === TaskPriority && pendingCommit.pendingWorkPriority !== TaskPriority)) {
522 - nextUnitOfWork = commitAllWork(pendingCommit);
523 - } else if (nextUnitOfWork && nextPriorityLevel !== NoWork && nextPriorityLevel <= priorityLevel) {
524 - nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
525 - } else {
526 - return;
527 - }
528 - }
529 - }
579
531 - function deferredWorkLoop(deadline) {
532 - if (!nextUnitOfWork) {
533 - nextUnitOfWork = findNextUnitOfWork();
534 - }
580 + // If there are errors, use a forked version of performUnitOfWork
581 + // TODO: Need to make commitAllWork throw on error before doing this
582 + if (capturedErrors && capturedErrors.size) {
583 + while (true) {
584 + if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
585 + if (!nextUnitOfWork && !pendingCommit) {
586 + nextUnitOfWork = findNextUnitOfWork();
587 + }
588 + if (pendingCommit) {
589 + commitAllWork(pendingCommit);
590 + } else if (nextUnitOfWork) {
591 + if (capturedErrors && capturedErrors.has(nextUnitOfWork)) {
592 + nextUnitOfWork = performFailedUnitOfWork(nextUnitOfWork);
593 + } else {
594 + nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
595 + }
596 + } else {
597 + return;
598 + }
599 + } else {
600 + return;
601 + }
602 + }
603 + }
604
536 - // If there are errors, use a forked version of performUnitOfWork
537 - // TODO: Need to make commitAllWork throw on error before doing this
538 - if (true || (capturedErrors && capturedErrors.size)) {
605 + // Otherwise, handle the normal, faster case where there are no errors:
606 while (true) {
607 if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
608 if (!nextUnitOfWork && !pendingCommit) {
609 nextUnitOfWork = findNextUnitOfWork();
610 }
611 if (pendingCommit) {
545 - nextUnitOfWork = commitAllWork(pendingCommit);
546 - } else if (nextUnitOfWork) {
547 - if (capturedErrors && capturedErrors.has(nextUnitOfWork)) {
548 - nextUnitOfWork = performFailedUnitOfWork(nextUnitOfWork);
549 - } else {
550 - nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
612 + const noError = commitAllWork(pendingCommit);
613 + if (!noError) {
614 + // There was an error during the commit phase. Restart the outer
615 + // loop so we can switch to the slower version of the work loop that
616 + // checks for failed work.
617 + continue outer;
618 }
619 + } else if (nextUnitOfWork) {
620 + nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
621 } else {
622 return;
623 }
@@ -557,24 +626,6 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
626 }
627 }
628 }
560 -
561 - // Otherwise, handle the normal, faster case where there are no errors:
562 - while (true) {
563 - if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
564 - if (!nextUnitOfWork && !pendingCommit) {
565 - nextUnitOfWork = findNextUnitOfWork();
566 - }
567 - if (pendingCommit) {
568 - nextUnitOfWork = commitAllWork(pendingCommit);
569 - } else if (nextUnitOfWork) {
570 - nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
571 - } else {
572 - return;
573 - }
574 - } else {
575 - return;
576 - }
577 - }
629 }
630
631 function performWork(priorityLevel : PriorityLevel, deadline : null | Deadline) {
@@ -585,51 +636,53 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
636
637 // Perform work until either there's no more work at this priority level, or
638 // (in the case of deferred work) we've run out of time.
588 - while (priorityLevel !== NoWork) {
589 - // Functions that contain a try-catch block are not optimizable by the
590 - // JS engine. The hottest code paths have been extracted to separate
591 - // functions, workLoop and deferredWorkLoop, which run on every unit of
592 - // work. The loop we're in now runs infrequently: to flush task work at
593 - // the end of a frame, or to restart after an error.
639 + while (true) {
640 try {
595 - if (priorityLevel >= HighPriority) {
596 - if (!deadline) {
597 - throw new Error('Cannot perform deferred work without a deadline.');
641 + // Functions that contain a try-catch block are not optimizable by the
642 + // JS engine. The hottest code paths have been extracted to separate
643 + // functions, workLoop and deferredWorkLoop, which run on every unit of
644 + // work. The loop we're in now runs infrequently: to flush task work at
645 + // the end of a frame, or to restart after an error.
646 + while (priorityLevel !== NoWork) {
647 + if (priorityLevel >= HighPriority) {
648 + if (!deadline) {
649 + throw new Error('Cannot perform deferred work without a deadline.');
650 + }
651 + // The deferred work loop will run until there's no time left in
652 + // the current frame
653 + deferredWorkLoop(deadline);
654 + } else {
655 + // The non-deferred work loop will run until there's no more work
656 + // at the given priority level
657 + workLoop(priorityLevel);
658 }
599 - // The deferred work loop will run until there's no time left in
600 - // the current frame
601 - deferredWorkLoop(deadline);
602 - } else {
603 - // The non-deferred work loop will run until there's no more work
604 - // at the given priority level
605 - workLoop(priorityLevel);
606 - }
659
608 - // Stop performing work
609 - priorityLevel = NoWork;
610 -
611 - // There might still be work left. Depending on the priority, we should
612 - // either perform it now or schedule a callback to perform it later.
613 - switch (nextPriorityLevel) {
614 - case SynchronousPriority:
615 - case TaskPriority:
616 - // Perform work immediately by switching the priority level
617 - // and continuing the loop.
618 - priorityLevel = nextPriorityLevel;
619 - break;
620 - case AnimationPriority:
621 - scheduleAnimationCallback(performAnimationWork);
622 - // Even though the next unit of work has animation priority, there
623 - // may still be deferred work left over as well. I think this is
624 - // only important for unit tests. In a real app, a deferred callback
625 - // would be scheduled during the next animation frame.
626 - scheduleDeferredCallback(performDeferredWork);
627 - break;
628 - case HighPriority:
629 - case LowPriority:
630 - case OffscreenPriority:
631 - scheduleDeferredCallback(performDeferredWork);
632 - break;
660 + // Stop performing work
661 + priorityLevel = NoWork;
662 +
663 + // There might still be work left. Depending on the priority, we should
664 + // either perform it now or schedule a callback to perform it later.
665 + switch (nextPriorityLevel) {
666 + case SynchronousPriority:
667 + case TaskPriority:
668 + // Perform work immediately by switching the priority level
669 + // and continuing the loop.
670 + priorityLevel = nextPriorityLevel;
671 + break;
672 + case AnimationPriority:
673 + scheduleAnimationCallback(performAnimationWork);
674 + // Even though the next unit of work has animation priority, there
675 + // may still be deferred work left over as well. I think this is
676 + // only important for unit tests. In a real app, a deferred callback
677 + // would be scheduled during the next animation frame.
678 + scheduleDeferredCallback(performDeferredWork);
679 + break;
680 + case HighPriority:
681 + case LowPriority:
682 + case OffscreenPriority:
683 + scheduleDeferredCallback(performDeferredWork);
684 + break;
685 + }
686 }
687 } catch (error) {
688 // We caught an error during either the begin or complete phases.
@@ -637,7 +690,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
690
691 // "Capture" the error by finding the nearest boundary. If there is no
692 // error boundary, the nearest host container acts as one.
640 - const maybeBoundary = captureError(failedWork, error, false);
693 + const maybeBoundary = captureError(failedWork, error);
694 if (maybeBoundary) {
695 const boundary = maybeBoundary;
696
@@ -663,8 +716,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
716 }
717 nextUnitOfWork = completeUnitOfWork(boundary);
718 }
666 - // The loop will continue performing work
719 + // Continue performing work
720 + continue;
721 }
722 + break;
723 }
724
725 // We're done performing work. Time to clean up.
@@ -681,7 +736,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
736 }
737 }
738
684 - function captureError(failedWork : Fiber | null, error : Error, isUnmounting : boolean) : Fiber | null {
739 + function captureError(failedWork : Fiber | null, error : Error) : Fiber | null {
740 // It is no longer valid because we exited the user code.
741 ReactCurrentOwner.current = null;
742 // It is no longer valid because this unit of work failed.
@@ -762,7 +817,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
817 // an update to itself
818 instance.unstable_handleError(error);
819 } catch (e) {
765 - captureError(effectfulFiber, e, false);
820 + captureError(effectfulFiber, e);
821 }
822 return;
823 case HostRoot:
src/renderers/shared/fiber/ReactFiberUpdateQueue.js
+5 -11
@@ -66,25 +66,19 @@ exports.addCallbackToQueue = function(queue : UpdateQueue, callback: Function) :
66 return queue;
67 };
68
69 -exports.callCallbacks = function(queue : UpdateQueue, context : any) : Error | null {
69 +exports.callCallbacks = function(queue : UpdateQueue, context : any) {
70 let node : ?UpdateQueueNode = queue;
71 - let firstError = null;
71 while (node) {
72 const callback = node.callback;
73 if (callback) {
75 - try {
76 - if (typeof context !== 'undefined') {
77 - callback.call(context);
78 - } else {
79 - callback();
80 - }
81 - } catch (error) {
82 - firstError = firstError || error;
74 + if (typeof context !== 'undefined') {
75 + callback.call(context);
76 + } else {
77 + callback();
78 }
79 }
80 node = node.next;
81 }
87 - return firstError;
82 };
83
84 function getStateFromNode(node, instance, state, props) {
src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
+1 -2
@@ -1497,11 +1497,10 @@ describe('ReactIncremental', () => {
1497 ReactNoop.flush();
1498 }).toThrow('callback error');
1499
1500 - // Should call all callbacks, even though the second one throws
1500 + // The third callback isn't called because the second one throws
1501 expect(ops).toEqual([
1502 'first callback',
1503 'second callback',
1504 - 'third callback',
1504 ]);
1505 expect(instance.state.n).toEqual(3);
1506 });