Skip to content

Commit 1dd9e4e

Browse files
committed
flushSync: Exhaust queue even if something throws (#26366)
If something throws as a result of `flushSync`, and there's remaining work left in the queue, React should keep working until all the work is complete. If multiple errors are thrown, React will combine them into an AggregateError object and throw that. In environments where AggregateError is not available, React will rethrow in an async task. (All the evergreen runtimes support AggregateError.) The scenario where this happens is relatively rare, because `flushSync` will only throw if there's no error boundary to capture the error. DiffTrain build for [93c10df](93c10df)
1 parent a91d8c7 commit 1dd9e4e

18 files changed

+1871
-1567
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ef8bdbecb6dbb9743b895c2e867e5a5264dd6651
1+
93c10dfa6b0848c12189b773b59c77d74cad2a1a

compiled/facebook-www/React-dev.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ if (
2727
}
2828
"use strict";
2929

30-
var ReactVersion = "18.3.0-www-modern-fd5743d3";
30+
var ReactVersion = "18.3.0-www-modern-f87ae691";
3131

3232
// ATTENTION
3333
// When adding new symbols to this file,

compiled/facebook-www/ReactART-dev.classic.js

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
6969
return self;
7070
}
7171

72-
var ReactVersion = "18.3.0-www-classic-9d9aef36";
72+
var ReactVersion = "18.3.0-www-classic-600fa423";
7373

7474
var LegacyRoot = 0;
7575
var ConcurrentRoot = 1;
@@ -3652,46 +3652,71 @@ function flushSyncCallbacksOnlyInLegacyMode() {
36523652
function flushSyncCallbacks() {
36533653
if (!isFlushingSyncQueue && syncQueue !== null) {
36543654
// Prevent re-entrance.
3655-
isFlushingSyncQueue = true;
3656-
var i = 0;
3657-
var previousUpdatePriority = getCurrentUpdatePriority();
3658-
3659-
try {
3660-
var isSync = true;
3661-
var queue = syncQueue; // TODO: Is this necessary anymore? The only user code that runs in this
3662-
// queue is in the render or commit phases.
3655+
isFlushingSyncQueue = true; // Set the event priority to discrete
3656+
// TODO: Is this necessary anymore? The only user code that runs in this
3657+
// queue is in the render or commit phases, which already set the
3658+
// event priority. Should be able to remove.
36633659

3664-
setCurrentUpdatePriority(DiscreteEventPriority); // $FlowFixMe[incompatible-use] found when upgrading Flow
3660+
var previousUpdatePriority = getCurrentUpdatePriority();
3661+
setCurrentUpdatePriority(DiscreteEventPriority);
3662+
var errors = null;
3663+
var queue = syncQueue; // $FlowFixMe[incompatible-use] found when upgrading Flow
36653664

3666-
for (; i < queue.length; i++) {
3667-
// $FlowFixMe[incompatible-use] found when upgrading Flow
3668-
var callback = queue[i];
3665+
for (var i = 0; i < queue.length; i++) {
3666+
// $FlowFixMe[incompatible-use] found when upgrading Flow
3667+
var callback = queue[i];
36693668

3669+
try {
36703670
do {
3671-
// $FlowFixMe[incompatible-type] we bail out when we get a null
3671+
var isSync = true; // $FlowFixMe[incompatible-type] we bail out when we get a null
3672+
36723673
callback = callback(isSync);
36733674
} while (callback !== null);
3675+
} catch (error) {
3676+
// Collect errors so we can rethrow them at the end
3677+
if (errors === null) {
3678+
errors = [error];
3679+
} else {
3680+
errors.push(error);
3681+
}
36743682
}
3683+
}
36753684

3676-
syncQueue = null;
3677-
includesLegacySyncCallbacks = false;
3678-
} catch (error) {
3679-
// If something throws, leave the remaining callbacks on the queue.
3680-
if (syncQueue !== null) {
3681-
syncQueue = syncQueue.slice(i + 1);
3682-
} // Resume flushing in the next tick
3685+
syncQueue = null;
3686+
includesLegacySyncCallbacks = false;
3687+
setCurrentUpdatePriority(previousUpdatePriority);
3688+
isFlushingSyncQueue = false;
36833689

3684-
scheduleCallback$2(ImmediatePriority, flushSyncCallbacks);
3685-
throw error;
3686-
} finally {
3687-
setCurrentUpdatePriority(previousUpdatePriority);
3688-
isFlushingSyncQueue = false;
3690+
if (errors !== null) {
3691+
if (errors.length > 1) {
3692+
if (typeof AggregateError === "function") {
3693+
// eslint-disable-next-line no-undef
3694+
throw new AggregateError(errors);
3695+
} else {
3696+
for (var _i = 1; _i < errors.length; _i++) {
3697+
scheduleCallback$2(
3698+
ImmediatePriority,
3699+
throwError.bind(null, errors[_i])
3700+
);
3701+
}
3702+
3703+
var firstError = errors[0];
3704+
throw firstError;
3705+
}
3706+
} else {
3707+
var error = errors[0];
3708+
throw error;
3709+
}
36893710
}
36903711
}
36913712

36923713
return null;
36933714
}
36943715

3716+
function throwError(error) {
3717+
throw error;
3718+
}
3719+
36953720
var nativeConsole = console;
36963721
var nativeConsoleLog = null;
36973722
var pendingGroupArgs = [];

compiled/facebook-www/ReactART-dev.modern.js

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
6969
return self;
7070
}
7171

72-
var ReactVersion = "18.3.0-www-modern-9e979f3e";
72+
var ReactVersion = "18.3.0-www-modern-ecb0bb1c";
7373

7474
var LegacyRoot = 0;
7575
var ConcurrentRoot = 1;
@@ -3408,46 +3408,71 @@ function flushSyncCallbacksOnlyInLegacyMode() {
34083408
function flushSyncCallbacks() {
34093409
if (!isFlushingSyncQueue && syncQueue !== null) {
34103410
// Prevent re-entrance.
3411-
isFlushingSyncQueue = true;
3412-
var i = 0;
3413-
var previousUpdatePriority = getCurrentUpdatePriority();
3414-
3415-
try {
3416-
var isSync = true;
3417-
var queue = syncQueue; // TODO: Is this necessary anymore? The only user code that runs in this
3418-
// queue is in the render or commit phases.
3411+
isFlushingSyncQueue = true; // Set the event priority to discrete
3412+
// TODO: Is this necessary anymore? The only user code that runs in this
3413+
// queue is in the render or commit phases, which already set the
3414+
// event priority. Should be able to remove.
34193415

3420-
setCurrentUpdatePriority(DiscreteEventPriority); // $FlowFixMe[incompatible-use] found when upgrading Flow
3416+
var previousUpdatePriority = getCurrentUpdatePriority();
3417+
setCurrentUpdatePriority(DiscreteEventPriority);
3418+
var errors = null;
3419+
var queue = syncQueue; // $FlowFixMe[incompatible-use] found when upgrading Flow
34213420

3422-
for (; i < queue.length; i++) {
3423-
// $FlowFixMe[incompatible-use] found when upgrading Flow
3424-
var callback = queue[i];
3421+
for (var i = 0; i < queue.length; i++) {
3422+
// $FlowFixMe[incompatible-use] found when upgrading Flow
3423+
var callback = queue[i];
34253424

3425+
try {
34263426
do {
3427-
// $FlowFixMe[incompatible-type] we bail out when we get a null
3427+
var isSync = true; // $FlowFixMe[incompatible-type] we bail out when we get a null
3428+
34283429
callback = callback(isSync);
34293430
} while (callback !== null);
3431+
} catch (error) {
3432+
// Collect errors so we can rethrow them at the end
3433+
if (errors === null) {
3434+
errors = [error];
3435+
} else {
3436+
errors.push(error);
3437+
}
34303438
}
3439+
}
34313440

3432-
syncQueue = null;
3433-
includesLegacySyncCallbacks = false;
3434-
} catch (error) {
3435-
// If something throws, leave the remaining callbacks on the queue.
3436-
if (syncQueue !== null) {
3437-
syncQueue = syncQueue.slice(i + 1);
3438-
} // Resume flushing in the next tick
3441+
syncQueue = null;
3442+
includesLegacySyncCallbacks = false;
3443+
setCurrentUpdatePriority(previousUpdatePriority);
3444+
isFlushingSyncQueue = false;
34393445

3440-
scheduleCallback$2(ImmediatePriority, flushSyncCallbacks);
3441-
throw error;
3442-
} finally {
3443-
setCurrentUpdatePriority(previousUpdatePriority);
3444-
isFlushingSyncQueue = false;
3446+
if (errors !== null) {
3447+
if (errors.length > 1) {
3448+
if (typeof AggregateError === "function") {
3449+
// eslint-disable-next-line no-undef
3450+
throw new AggregateError(errors);
3451+
} else {
3452+
for (var _i = 1; _i < errors.length; _i++) {
3453+
scheduleCallback$2(
3454+
ImmediatePriority,
3455+
throwError.bind(null, errors[_i])
3456+
);
3457+
}
3458+
3459+
var firstError = errors[0];
3460+
throw firstError;
3461+
}
3462+
} else {
3463+
var error = errors[0];
3464+
throw error;
3465+
}
34453466
}
34463467
}
34473468

34483469
return null;
34493470
}
34503471

3472+
function throwError(error) {
3473+
throw error;
3474+
}
3475+
34513476
var nativeConsole = console;
34523477
var nativeConsoleLog = null;
34533478
var pendingGroupArgs = [];

0 commit comments

Comments
 (0)