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
});