Skip to content

Commit 4ad3673

Browse files
authored
fix(waitFor): handle odd timing issue with fake timers (#667)
1 parent 2fe177f commit 4ad3673

File tree

2 files changed

+30
-5
lines changed

2 files changed

+30
-5
lines changed

src/__tests__/fake-timers.js

+17-3
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ afterAll(() => {
99
jest.useRealTimers()
1010
})
1111

12-
async function runWaitFor() {
12+
async function runWaitFor({time = 300} = {}, options) {
1313
const response = 'data'
1414
const doAsyncThing = () =>
15-
new Promise(r => setTimeout(() => r(response), 300))
15+
new Promise(r => setTimeout(() => r(response), time))
1616
let result
1717
doAsyncThing().then(r => (result = r))
1818

19-
await waitFor(() => expect(result).toBe(response))
19+
await waitFor(() => expect(result).toBe(response), options)
2020
}
2121

2222
test('real timers', async () => {
@@ -64,3 +64,17 @@ test('times out after 1000ms by default', async () => {
6464
// that we're using real timers.
6565
expect(performance.now() - start).toBeGreaterThanOrEqual(900)
6666
})
67+
68+
test('recursive timers do not cause issues', async () => {
69+
let recurse = true
70+
function startTimer() {
71+
setTimeout(() => {
72+
if (recurse) startTimer()
73+
}, 1)
74+
}
75+
76+
startTimer()
77+
await runWaitFor({time: 800}, {timeout: 100})
78+
79+
recurse = false
80+
})

src/wait-for.js

+13-2
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,25 @@ function waitFor(
5252
// waiting or when we've timed out.
5353
// eslint-disable-next-line no-unmodified-loop-condition
5454
while (!finished) {
55+
// we *could* (maybe should?) use `advanceTimersToNextTimer` but it's
56+
// possible that could make this loop go on forever if someone is using
57+
// third party code that's setting up recursive timers so rapidly that
58+
// the user's timer's don't get a chance to resolve. So we'll advance
59+
// by an interval instead. (We have a test for this case).
5560
jest.advanceTimersByTime(interval)
56-
// in this rare case, we *need* to wait for in-flight promises
61+
62+
// It's really important that checkCallback is run *before* we flush
63+
// in-flight promises. To be honest, I'm not sure why, and I can't quite
64+
// think of a way to reproduce the problem in a test, but I spent
65+
// an entire day banging my head against a wall on this.
66+
checkCallback()
67+
68+
// In this rare case, we *need* to wait for in-flight promises
5769
// to resolve before continuing. We don't need to take advantage
5870
// of parallelization so we're fine.
5971
// https://stackoverflow.com/a/59243586/971592
6072
// eslint-disable-next-line no-await-in-loop
6173
await new Promise(r => setImmediate(r))
62-
checkCallback()
6374
}
6475
} else {
6576
intervalId = setInterval(checkCallback, interval)

0 commit comments

Comments
 (0)