-
Notifications
You must be signed in to change notification settings - Fork 46.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
flushSync: Exhaust queue even if something throws
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.
- Loading branch information
Showing
5 changed files
with
204 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
packages/react-reconciler/src/__tests__/ReactFlushSyncNoAggregateError-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
let React; | ||
let ReactNoop; | ||
let Scheduler; | ||
let act; | ||
let assertLog; | ||
let waitForThrow; | ||
|
||
// TODO: Migrate tests to React DOM instead of React Noop | ||
|
||
describe('ReactFlushSync (AggregateError not available)', () => { | ||
beforeEach(() => { | ||
jest.resetModules(); | ||
|
||
global.AggregateError = undefined; | ||
|
||
React = require('react'); | ||
ReactNoop = require('react-noop-renderer'); | ||
Scheduler = require('scheduler'); | ||
act = require('internal-test-utils').act; | ||
|
||
const InternalTestUtils = require('internal-test-utils'); | ||
assertLog = InternalTestUtils.assertLog; | ||
waitForThrow = InternalTestUtils.waitForThrow; | ||
}); | ||
|
||
function Text({text}) { | ||
Scheduler.log(text); | ||
return text; | ||
} | ||
|
||
test('completely exhausts synchronous work queue even if something throws', async () => { | ||
function Throws({error}) { | ||
throw error; | ||
} | ||
|
||
const root1 = ReactNoop.createRoot(); | ||
const root2 = ReactNoop.createRoot(); | ||
const root3 = ReactNoop.createRoot(); | ||
|
||
await act(async () => { | ||
root1.render(<Text text="Hi" />); | ||
root2.render(<Text text="Andrew" />); | ||
root3.render(<Text text="!" />); | ||
}); | ||
assertLog(['Hi', 'Andrew', '!']); | ||
|
||
const aahh = new Error('AAHH!'); | ||
const nooo = new Error('Noooooooooo!'); | ||
|
||
let error; | ||
try { | ||
ReactNoop.flushSync(() => { | ||
root1.render(<Throws error={aahh} />); | ||
root2.render(<Throws error={nooo} />); | ||
root3.render(<Text text="aww" />); | ||
}); | ||
} catch (e) { | ||
error = e; | ||
} | ||
|
||
// The update to root 3 should have finished synchronously, even though the | ||
// earlier updates errored. | ||
assertLog(['aww']); | ||
// Roots 1 and 2 were unmounted. | ||
expect(root1).toMatchRenderedOutput(null); | ||
expect(root2).toMatchRenderedOutput(null); | ||
expect(root3).toMatchRenderedOutput('aww'); | ||
|
||
// In modern environments, React would throw an AggregateError. Because | ||
// AggregateError is not available, React throws the first error, then | ||
// throws the remaining errors in separate tasks. | ||
expect(error).toBe(aahh); | ||
// TODO: Currently the remaining error is rethrown in an Immediate Scheduler | ||
// task, but this may change to a timer or microtask in the future. The | ||
// exact mechanism is an implementation detail; they just need to be logged | ||
// in the order the occurred. | ||
await waitForThrow(nooo); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters