-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
throttled.ts
94 lines (85 loc) · 2.38 KB
/
throttled.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
type State<TArgs extends readonly unknown[]> =
| {
readonly args: TArgs;
readonly handle: any;
readonly key: 'scheduled';
}
| {
readonly handle: any;
readonly key: 'waiting';
}
| {
readonly key: 'idle';
};
type Throttled<TArgs extends readonly unknown[]> = {
(...args: TArgs): void;
/**
* If a call to the wrapped callback is scheduled, cancel it. Has no effect
* if no call is scheduled.
*/
cancel(): void;
/**
* If a call to the wrapped callback is scheduled, call it immediately
* instead of waiting for the timeout to elapse. Has no effect if no call is
* scheduled.
*/
flush(): void;
};
/**
* Wrap a callback so that it is never called more than once in `timeout`
* milliseconds. The wrapped callback will be called immediately if possible.
* Otherwise, it will be scheduled to run as soon as possible. If the
* throttled wrapper is called while a call is already scheduled, the
* scheduled arguments are updated, but the scheduled time remains the same.
*/
const createThrottled = <TArgs extends readonly unknown[]>(
timeout: number,
callback: (...args: TArgs) => void,
): Throttled<TArgs> => {
let state: State<TArgs> = { key: 'idle' };
const onTimeout = (current: State<TArgs>): void => {
switch (current.key) {
case 'scheduled':
state = { handle: startTimeout(), key: 'waiting' };
callback(...current.args);
break;
case 'waiting':
state = { key: 'idle' };
break;
/* c8 ignore next */
case 'idle':
/* c8 ignore next */
break;
}
};
const startTimeout = (): ReturnType<typeof setTimeout> => {
return setTimeout(() => {
onTimeout(state);
}, timeout);
};
const throttled = (...newArgs: TArgs): void => {
switch (state.key) {
case 'scheduled':
case 'waiting':
state = { args: newArgs, handle: state.handle, key: 'scheduled' };
break;
case 'idle':
state = { handle: startTimeout(), key: 'waiting' };
callback(...newArgs);
break;
}
};
throttled.cancel = () => {
if (state.key === 'scheduled') {
state = { handle: state.handle, key: 'waiting' };
}
};
throttled.flush = () => {
if (state.key === 'scheduled') {
clearTimeout(state.handle);
onTimeout(state);
}
};
return throttled;
};
export { createThrottled };