-
-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathindex.js
116 lines (90 loc) · 2.6 KB
/
index.js
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
const registry = new FinalizationRegistry(({signal, aborted}) => {
signal?.removeEventListener('abort', aborted);
});
export default function pThrottle({limit, interval, strict, signal, onDelay}) {
if (!Number.isFinite(limit)) {
throw new TypeError('Expected `limit` to be a finite number');
}
if (!Number.isFinite(interval)) {
throw new TypeError('Expected `interval` to be a finite number');
}
const queue = new Map();
let currentTick = 0;
let activeCount = 0;
function windowedDelay() {
const now = Date.now();
if ((now - currentTick) > interval) {
activeCount = 1;
currentTick = now;
return 0;
}
if (activeCount < limit) {
activeCount++;
} else {
currentTick += interval;
activeCount = 1;
}
return currentTick - now;
}
const strictTicks = [];
function strictDelay() {
const now = Date.now();
// Clear the queue if there's a significant delay since the last execution
if (strictTicks.length > 0 && now - strictTicks.at(-1) > interval) {
strictTicks.length = 0;
}
// If the queue is not full, add the current time and execute immediately
if (strictTicks.length < limit) {
strictTicks.push(now);
return 0;
}
// Calculate the next execution time based on the first item in the queue
const nextExecutionTime = strictTicks[0] + interval;
// Shift the queue and add the new execution time
strictTicks.shift();
strictTicks.push(nextExecutionTime);
// Calculate the delay for the current execution
return Math.max(0, nextExecutionTime - now);
}
const getDelay = strict ? strictDelay : windowedDelay;
return function_ => {
const throttled = function (...arguments_) {
if (!throttled.isEnabled) {
return (async () => function_.apply(this, arguments_))();
}
let timeoutId;
return new Promise((resolve, reject) => {
const execute = () => {
resolve(function_.apply(this, arguments_));
queue.delete(timeoutId);
};
const delay = getDelay();
if (delay > 0) {
timeoutId = setTimeout(execute, delay);
queue.set(timeoutId, reject);
onDelay?.(...arguments_);
} else {
execute();
}
});
};
const aborted = () => {
for (const timeout of queue.keys()) {
clearTimeout(timeout);
queue.get(timeout)(signal.reason);
}
queue.clear();
strictTicks.splice(0, strictTicks.length);
};
registry.register(throttled, {signal, aborted});
signal?.throwIfAborted();
signal?.addEventListener('abort', aborted, {once: true});
throttled.isEnabled = true;
Object.defineProperty(throttled, 'queueSize', {
get() {
return queue.size;
},
});
return throttled;
};
}