Skip to content

Commit 394f65a

Browse files
authored
refactor: async utils (#537)
* feat: use default values for interval and timeout in async utils BREAKING CHANGE: `interval` will now default to 50ms in async utils BREAKING CHANGE: `timeout` will now default to 1000ms in async utils BREAKING CHANGE: `suppressErrors` has been removed from async utils * refactor: move timeoutPromise into callAfter helper * fix: react is not an optional peerDependency * refactor: return boolean instead of throwing error to handle timeout
1 parent bab38d9 commit 394f65a

File tree

8 files changed

+385
-369
lines changed

8 files changed

+385
-369
lines changed

docs/api-reference.md

+18-24
Original file line numberDiff line numberDiff line change
@@ -201,25 +201,26 @@ removed, the provided callback will no longer execute as part of running
201201
### `waitForNextUpdate`
202202

203203
```ts
204-
function waitForNextUpdate(options?: { timeout?: number }): Promise<void>
204+
function waitForNextUpdate(options?: { timeout?: number | false }): Promise<void>
205205
```
206206

207207
Returns a `Promise` that resolves the next time the hook renders, commonly when state is updated as
208208
the result of an asynchronous update.
209209

210210
#### `timeout`
211211

212-
The maximum amount of time in milliseconds (ms) to wait. By default, no timeout is applied.
212+
_Default: 1000_
213+
214+
The maximum amount of time in milliseconds (ms) to wait.
213215

214216
### `waitFor`
215217

216218
```ts
217219
function waitFor(
218220
callback: () => boolean | void,
219221
options?: {
220-
interval?: number
221-
timeout?: number
222-
suppressErrors?: boolean
222+
interval?: number | false
223+
timeout?: number | false
223224
}
224225
): Promise<void>
225226
```
@@ -230,29 +231,25 @@ in the callback to perform assertion or to test values.
230231

231232
#### `interval`
232233

234+
_Default: 50_
235+
233236
The amount of time in milliseconds (ms) to wait between checks of the callback if no renders occur.
234-
Interval checking is disabled if `interval` is not provided in the options or provided as a `falsy`
235-
value. By default, it is disabled.
237+
Interval checking is disabled if `interval` is not provided as a `falsy`.
236238

237239
#### `timeout`
238240

239-
The maximum amount of time in milliseconds (ms) to wait. By default, no timeout is applied.
240-
241-
#### `suppressErrors`
241+
_Default: 1000_
242242

243-
If this option is set to `true`, any errors that occur while waiting are treated as a failed check.
244-
If this option is set to `false`, any errors that occur while waiting cause the promise to be
245-
rejected. By default, errors are suppressed for this utility.
243+
The maximum amount of time in milliseconds (ms) to wait.
246244

247245
### `waitForValueToChange`
248246

249247
```ts
250248
function waitForValueToChange(
251249
selector: () => any,
252250
options?: {
253-
interval?: number
254-
timeout?: number
255-
suppressErrors?: boolean
251+
interval?: number | false
252+
timeout?: number | false
256253
}
257254
): Promise<void>
258255
```
@@ -263,16 +260,13 @@ for comparison.
263260

264261
#### `interval`
265262

263+
_Default: 50_
264+
266265
The amount of time in milliseconds (ms) to wait between checks of the callback if no renders occur.
267-
Interval checking is disabled if `interval` is not provided in the options or provided as a `falsy`
268-
value. By default, it is disabled.
266+
Interval checking is disabled if `interval` is not provided as a `falsy`.
269267

270268
#### `timeout`
271269

272-
The maximum amount of time in milliseconds (ms) to wait. By default, no timeout is applied.
273-
274-
#### `suppressErrors`
270+
_Default: 1000_
275271

276-
If this option is set to `true`, any errors that occur while waiting are treated as a failed check.
277-
If this option is set to `false`, any errors that occur while waiting cause the promise to be
278-
rejected. By default, errors are not suppressed for this utility.
272+
The maximum amount of time in milliseconds (ms) to wait.

package.json

-3
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,6 @@
7070
"react-test-renderer": ">=16.9.0"
7171
},
7272
"peerDependenciesMeta": {
73-
"react": {
74-
"optional": true
75-
},
7673
"react-dom": {
7774
"optional": true
7875
},

src/core/asyncUtils.ts

+77-68
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,106 @@
1-
import { Act, WaitOptions, AsyncUtils } from '../types'
1+
import {
2+
Act,
3+
WaitOptions,
4+
WaitForOptions,
5+
WaitForValueToChangeOptions,
6+
WaitForNextUpdateOptions,
7+
AsyncUtils
8+
} from '../types'
29

3-
import { resolveAfter } from '../helpers/promises'
10+
import { resolveAfter, callAfter } from '../helpers/promises'
411
import { TimeoutError } from '../helpers/error'
512

13+
const DEFAULT_INTERVAL = 50
14+
const DEFAULT_TIMEOUT = 1000
15+
616
function asyncUtils(act: Act, addResolver: (callback: () => void) => void): AsyncUtils {
7-
let nextUpdatePromise: Promise<void> | null = null
8-
9-
const waitForNextUpdate = async ({ timeout }: Pick<WaitOptions, 'timeout'> = {}) => {
10-
if (nextUpdatePromise) {
11-
await nextUpdatePromise
12-
} else {
13-
nextUpdatePromise = new Promise((resolve, reject) => {
14-
let timeoutId: ReturnType<typeof setTimeout>
15-
if (timeout && timeout > 0) {
16-
timeoutId = setTimeout(
17-
() => reject(new TimeoutError(waitForNextUpdate, timeout)),
18-
timeout
19-
)
17+
const wait = async (callback: () => boolean | void, { interval, timeout }: WaitOptions) => {
18+
const checkResult = () => {
19+
const callbackResult = callback()
20+
return callbackResult ?? callbackResult === undefined
21+
}
22+
23+
const waitForResult = async () => {
24+
while (true) {
25+
await Promise.race(
26+
[
27+
new Promise<void>((resolve) => addResolver(resolve)),
28+
interval && resolveAfter(interval)
29+
].filter(Boolean)
30+
)
31+
32+
if (checkResult()) {
33+
return
2034
}
21-
addResolver(() => {
22-
clearTimeout(timeoutId)
23-
nextUpdatePromise = null
24-
resolve()
25-
})
26-
})
27-
await act(() => nextUpdatePromise as Promise<void>)
35+
}
2836
}
37+
38+
let timedOut = false
39+
40+
if (!checkResult()) {
41+
if (timeout) {
42+
const timeoutPromise = () =>
43+
callAfter(() => {
44+
timedOut = true
45+
}, timeout)
46+
47+
await act(() => Promise.race([waitForResult(), timeoutPromise()]))
48+
} else {
49+
await act(waitForResult)
50+
}
51+
}
52+
53+
return !timedOut
2954
}
3055

3156
const waitFor = async (
3257
callback: () => boolean | void,
33-
{ interval, timeout, suppressErrors = true }: WaitOptions = {}
58+
{ interval = DEFAULT_INTERVAL, timeout = DEFAULT_TIMEOUT }: WaitForOptions = {}
3459
) => {
35-
const checkResult = () => {
60+
const safeCallback = () => {
3661
try {
37-
const callbackResult = callback()
38-
return callbackResult ?? callbackResult === undefined
62+
return callback()
3963
} catch (error: unknown) {
40-
if (!suppressErrors) {
41-
throw error
42-
}
43-
return undefined
64+
return false
4465
}
4566
}
4667

47-
const waitForResult = async () => {
48-
const initialTimeout = timeout
49-
while (true) {
50-
const startTime = Date.now()
51-
try {
52-
const nextCheck = interval
53-
? Promise.race([waitForNextUpdate({ timeout }), resolveAfter(interval)])
54-
: waitForNextUpdate({ timeout })
55-
56-
await nextCheck
57-
58-
if (checkResult()) {
59-
return
60-
}
61-
} catch (error: unknown) {
62-
if (error instanceof TimeoutError && initialTimeout) {
63-
throw new TimeoutError(waitFor, initialTimeout)
64-
}
65-
throw error
66-
}
67-
if (timeout) timeout -= Date.now() - startTime
68-
}
68+
const result = await wait(safeCallback, { interval, timeout })
69+
if (!result && timeout) {
70+
throw new TimeoutError(waitFor, timeout)
6971
}
72+
}
7073

71-
if (!checkResult()) {
72-
await waitForResult()
74+
const waitForValueToChange = async (
75+
selector: () => unknown,
76+
{ interval = DEFAULT_INTERVAL, timeout = DEFAULT_TIMEOUT }: WaitForValueToChangeOptions = {}
77+
) => {
78+
const initialValue = selector()
79+
80+
const result = await wait(() => selector() !== initialValue, { interval, timeout })
81+
if (!result && timeout) {
82+
throw new TimeoutError(waitForValueToChange, timeout)
7383
}
7484
}
7585

76-
const waitForValueToChange = async (selector: () => unknown, options: WaitOptions = {}) => {
77-
const initialValue = selector()
78-
try {
79-
await waitFor(() => selector() !== initialValue, {
80-
suppressErrors: false,
81-
...options
82-
})
83-
} catch (error: unknown) {
84-
if (error instanceof TimeoutError && options.timeout) {
85-
throw new TimeoutError(waitForValueToChange, options.timeout)
86-
}
87-
throw error
86+
const waitForNextUpdate = async ({
87+
timeout = DEFAULT_TIMEOUT
88+
}: WaitForNextUpdateOptions = {}) => {
89+
let updated = false
90+
addResolver(() => {
91+
updated = true
92+
})
93+
94+
const result = await wait(() => updated, { interval: false, timeout })
95+
if (!result && timeout) {
96+
throw new TimeoutError(waitForNextUpdate, timeout)
8897
}
8998
}
9099

91100
return {
92101
waitFor,
93-
waitForNextUpdate,
94-
waitForValueToChange
102+
waitForValueToChange,
103+
waitForNextUpdate
95104
}
96105
}
97106

0 commit comments

Comments
 (0)