@common.js/p-throttle 6.1.0 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +1 -1
  2. package/index.d.ts +98 -25
  3. package/index.js +307 -202
  4. package/package.json +10 -8
package/README.md CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  The [p-throttle](https://www.npmjs.com/package/p-throttle) package exported as CommonJS modules.
4
4
 
5
- Exported from [p-throttle@6.1.0](https://www.npmjs.com/package/p-throttle/v/6.1.0) using https://github.com/etienne-martin/common.js.
5
+ Exported from [p-throttle@8.1.0](https://www.npmjs.com/package/p-throttle/v/8.1.0) using https://github.com/etienne-martin/common.js.
package/index.d.ts CHANGED
@@ -1,14 +1,8 @@
1
- export class AbortError extends Error {
2
- readonly name: 'AbortError';
3
-
4
- private constructor();
5
- }
6
-
7
1
  type AnyFunction = (...arguments_: readonly any[]) => unknown;
8
2
 
9
3
  export type ThrottledFunction<F extends AnyFunction> = F & {
10
4
  /**
11
- Whether future function calls should be throttled or count towards throttling thresholds.
5
+ Whether future function calls should be throttled and count towards throttling thresholds.
12
6
 
13
7
  @default true
14
8
  */
@@ -16,13 +10,28 @@ export type ThrottledFunction<F extends AnyFunction> = F & {
16
10
 
17
11
  /**
18
12
  The number of queued items waiting to be executed.
19
- */
20
- readonly queueSize: number;
21
13
 
22
- /**
23
- Abort pending executions. All unresolved promises are rejected with a `pThrottle.AbortError` error.
14
+ This can be useful for implementing queue management strategies, such as using a fallback when the queue is too full.
15
+
16
+ @example
17
+ ```
18
+ import pThrottle from 'p-throttle';
19
+
20
+ const throttle = pThrottle({limit: 1, interval: 1000});
21
+
22
+ const accurateData = throttle(() => fetch('https://accurate-api.example.com'));
23
+ const roughData = () => fetch('https://rough-api.example.com');
24
+
25
+ async function getData() {
26
+ if (accurateData.queueSize >= 3) {
27
+ return roughData(); // Queue full, use fallback
28
+ }
29
+
30
+ return accurateData();
31
+ }
32
+ ```
24
33
  */
25
- abort(): void;
34
+ readonly queueSize: number;
26
35
  };
27
36
 
28
37
  export type Options = {
@@ -37,47 +46,111 @@ export type Options = {
37
46
  readonly interval: number;
38
47
 
39
48
  /**
40
- Use a strict, more resource intensive, throttling algorithm. The default algorithm uses a windowed approach that will work correctly in most cases, limiting the total number of calls at the specified limit per interval window. The strict algorithm throttles each call individually, ensuring the limit is not exceeded for any interval.
49
+ Use a strict, more resource-intensive, throttling algorithm. The default algorithm uses a windowed approach that will work correctly in most cases, limiting the total number of calls at the specified limit per interval window. The strict algorithm throttles each call individually, ensuring the limit is not exceeded for any interval.
41
50
 
42
51
  @default false
43
52
  */
44
53
  readonly strict?: boolean;
45
54
 
46
55
  /**
47
- Get notified when function calls are delayed due to exceeding the `limit` of allowed calls within the given `interval`.
48
-
49
- Can be useful for monitoring the throttling efficiency.
56
+ Abort pending executions. When aborted, all unresolved promises are rejected with `signal.reason`.
50
57
 
51
58
  @example
52
59
  ```
53
60
  import pThrottle from 'p-throttle';
54
61
 
62
+ const controller = new AbortController();
63
+
55
64
  const throttle = pThrottle({
56
65
  limit: 2,
57
66
  interval: 1000,
58
- onDelay: () => {
59
- console.log('Reached interval limit, call is delayed');
60
- },
67
+ signal: controller.signal
61
68
  });
62
69
 
63
70
  const throttled = throttle(() => {
64
- console.log('Executing...');
65
- });
71
+ console.log('Executing...');
72
+ });
66
73
 
67
74
  await throttled();
68
75
  await throttled();
76
+ controller.abort('aborted');
69
77
  await throttled();
70
78
  //=> Executing...
71
79
  //=> Executing...
72
- //=> Reached interval limit, call is delayed
73
- //=> Executing...
80
+ //=> Promise rejected with reason `aborted`
81
+ ```
82
+ */
83
+ signal?: AbortSignal;
84
+
85
+ /**
86
+ Get notified when function calls are delayed due to exceeding the `limit` of allowed calls within the given `interval`.
87
+
88
+ The delayed call arguments are passed to the `onDelay` callback. Can be useful for monitoring the throttling efficiency.
89
+
90
+ @example
91
+ ```
92
+ import pThrottle from 'p-throttle';
93
+
94
+ const throttle = pThrottle({
95
+ limit: 2,
96
+ interval: 1000,
97
+ onDelay: (a, b) => {
98
+ console.log(`Reached interval limit, call is delayed for ${a} ${b}`);
99
+ },
100
+ });
101
+
102
+ const throttled = throttle((a, b) => {
103
+ console.log(`Executing with ${a} ${b}...`);
104
+ });
105
+
106
+ await throttled(1, 2);
107
+ await throttled(3, 4);
108
+ await throttled(5, 6);
109
+ //=> Executing with 1 2...
110
+ //=> Executing with 3 4...
111
+ //=> Reached interval limit, call is delayed for 5 6
112
+ //=> Executing with 5 6...
113
+ ```
114
+ */
115
+ readonly onDelay?: (...arguments_: readonly any[]) => void;
116
+
117
+ /**
118
+ Calculate the weight/cost of each function call based on its arguments.
119
+
120
+ The weight determines how much of the `limit` is consumed by each call. This is useful for rate limiting APIs that use point-based or cost-based limits, where different operations consume different amounts of the quota.
121
+
122
+ By default, each call has a weight of `1`.
123
+
124
+ In the following example, queries with different numbers of tables consume different amounts of the rate limit:
125
+
126
+ @example
127
+ ```
128
+ import pThrottle from 'p-throttle';
129
+
130
+ // Storyblok GraphQL API: 100 points per second
131
+ // Each query costs 1 point for the connection plus 1 point per table
132
+ const throttle = pThrottle({
133
+ limit: 100,
134
+ interval: 1000,
135
+ weight: numberOfTables => 1 + numberOfTables
136
+ });
137
+
138
+ const fetchData = throttle(numberOfTables => {
139
+ // Fetch GraphQL data
140
+ return fetch('...');
141
+ });
142
+
143
+ await fetchData(1); // Costs 2 points
144
+ await fetchData(3); // Costs 4 points
74
145
  ```
75
146
  */
76
- readonly onDelay?: () => void;
147
+ readonly weight?: (...arguments_: readonly any[]) => number;
77
148
  };
78
149
 
79
150
  /**
80
- Throttle promise-returning/async/normal functions.
151
+ Throttle promise-returning & async functions.
152
+
153
+ Also works with normal functions.
81
154
 
82
155
  It rate-limits function calls without discarding them, making it ideal for external API interactions where avoiding call loss is crucial.
83
156
 
package/index.js CHANGED
@@ -2,25 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
- function _export(target, all) {
6
- for(var name in all)Object.defineProperty(target, name, {
7
- enumerable: true,
8
- get: all[name]
9
- });
10
- }
11
- _export(exports, {
12
- AbortError: function() {
13
- return AbortError;
14
- },
15
- default: function() {
5
+ Object.defineProperty(exports, "default", {
6
+ enumerable: true,
7
+ get: function() {
16
8
  return pThrottle;
17
9
  }
18
10
  });
19
- function _assertThisInitialized(self) {
20
- if (self === void 0) {
21
- throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
22
- }
23
- return self;
11
+ function _arrayLikeToArray(arr, len) {
12
+ if (len == null || len > arr.length) len = arr.length;
13
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
14
+ return arr2;
15
+ }
16
+ function _arrayWithoutHoles(arr) {
17
+ if (Array.isArray(arr)) return _arrayLikeToArray(arr);
24
18
  }
25
19
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
26
20
  try {
@@ -51,127 +45,22 @@ function _asyncToGenerator(fn) {
51
45
  });
52
46
  };
53
47
  }
54
- function _classCallCheck(instance, Constructor) {
55
- if (!(instance instanceof Constructor)) {
56
- throw new TypeError("Cannot call a class as a function");
57
- }
58
- }
59
- function isNativeReflectConstruct() {
60
- if (typeof Reflect === "undefined" || !Reflect.construct) return false;
61
- if (Reflect.construct.sham) return false;
62
- if (typeof Proxy === "function") return true;
63
- try {
64
- Date.prototype.toString.call(Reflect.construct(Date, [], function() {}));
65
- return true;
66
- } catch (e) {
67
- return false;
68
- }
69
- }
70
- function _construct(Parent, args, Class) {
71
- if (isNativeReflectConstruct()) {
72
- _construct = Reflect.construct;
73
- } else {
74
- _construct = function _construct(Parent, args, Class) {
75
- var a = [
76
- null
77
- ];
78
- a.push.apply(a, args);
79
- var Constructor = Function.bind.apply(Parent, a);
80
- var instance = new Constructor();
81
- if (Class) _setPrototypeOf(instance, Class.prototype);
82
- return instance;
83
- };
84
- }
85
- return _construct.apply(null, arguments);
86
- }
87
- function _getPrototypeOf(o) {
88
- _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
89
- return o.__proto__ || Object.getPrototypeOf(o);
90
- };
91
- return _getPrototypeOf(o);
92
- }
93
- function _inherits(subClass, superClass) {
94
- if (typeof superClass !== "function" && superClass !== null) {
95
- throw new TypeError("Super expression must either be null or a function");
96
- }
97
- subClass.prototype = Object.create(superClass && superClass.prototype, {
98
- constructor: {
99
- value: subClass,
100
- writable: true,
101
- configurable: true
102
- }
103
- });
104
- if (superClass) _setPrototypeOf(subClass, superClass);
105
- }
106
- function _isNativeFunction(fn) {
107
- return Function.toString.call(fn).indexOf("[native code]") !== -1;
108
- }
109
- function _possibleConstructorReturn(self, call) {
110
- if (call && (_typeof(call) === "object" || typeof call === "function")) {
111
- return call;
112
- }
113
- return _assertThisInitialized(self);
114
- }
115
- function _setPrototypeOf(o, p) {
116
- _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
117
- o.__proto__ = p;
118
- return o;
119
- };
120
- return _setPrototypeOf(o, p);
48
+ function _iterableToArray(iter) {
49
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
121
50
  }
122
- var _typeof = function(obj) {
123
- "@swc/helpers - typeof";
124
- return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
125
- };
126
- function _wrapNativeSuper(Class) {
127
- var _cache = typeof Map === "function" ? new Map() : undefined;
128
- _wrapNativeSuper = function _wrapNativeSuper(Class) {
129
- if (Class === null || !_isNativeFunction(Class)) return Class;
130
- if (typeof Class !== "function") {
131
- throw new TypeError("Super expression must either be null or a function");
132
- }
133
- if (typeof _cache !== "undefined") {
134
- if (_cache.has(Class)) return _cache.get(Class);
135
- _cache.set(Class, Wrapper);
136
- }
137
- function Wrapper() {
138
- return _construct(Class, arguments, _getPrototypeOf(this).constructor);
139
- }
140
- Wrapper.prototype = Object.create(Class.prototype, {
141
- constructor: {
142
- value: Wrapper,
143
- enumerable: false,
144
- writable: true,
145
- configurable: true
146
- }
147
- });
148
- return _setPrototypeOf(Wrapper, Class);
149
- };
150
- return _wrapNativeSuper(Class);
51
+ function _nonIterableSpread() {
52
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
151
53
  }
152
- function _isNativeReflectConstruct() {
153
- if (typeof Reflect === "undefined" || !Reflect.construct) return false;
154
- if (Reflect.construct.sham) return false;
155
- if (typeof Proxy === "function") return true;
156
- try {
157
- Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function() {}));
158
- return true;
159
- } catch (e) {
160
- return false;
161
- }
54
+ function _toConsumableArray(arr) {
55
+ return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
162
56
  }
163
- function _createSuper(Derived) {
164
- var hasNativeReflectConstruct = _isNativeReflectConstruct();
165
- return function _createSuperInternal() {
166
- var Super = _getPrototypeOf(Derived), result;
167
- if (hasNativeReflectConstruct) {
168
- var NewTarget = _getPrototypeOf(this).constructor;
169
- result = Reflect.construct(Super, arguments, NewTarget);
170
- } else {
171
- result = Super.apply(this, arguments);
172
- }
173
- return _possibleConstructorReturn(this, result);
174
- };
57
+ function _unsupportedIterableToArray(o, minLen) {
58
+ if (!o) return;
59
+ if (typeof o === "string") return _arrayLikeToArray(o, minLen);
60
+ var n = Object.prototype.toString.call(o).slice(8, -1);
61
+ if (n === "Object" && o.constructor) n = o.constructor.name;
62
+ if (n === "Map" || n === "Set") return Array.from(n);
63
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
175
64
  }
176
65
  var __generator = (void 0) && (void 0).__generator || function(thisArg, body) {
177
66
  var f, y, t, g, _ = {
@@ -268,54 +157,142 @@ var __generator = (void 0) && (void 0).__generator || function(thisArg, body) {
268
157
  };
269
158
  }
270
159
  };
271
- var AbortError = /*#__PURE__*/ function(Error1) {
272
- "use strict";
273
- _inherits(AbortError, Error1);
274
- var _super = _createSuper(AbortError);
275
- function AbortError() {
276
- _classCallCheck(this, AbortError);
277
- var _this;
278
- _this = _super.call(this, "Throttled function aborted");
279
- _this.name = "AbortError";
280
- return _this;
160
+ var states = new WeakMap();
161
+ var signalThrottleds = new WeakMap(); // AbortSignal -> {throttleds: Set<WeakRef>, listener: Function}
162
+ var finalizationRegistry = new FinalizationRegistry(function(param) {
163
+ var signalWeakRef = param.signalWeakRef, weakReference = param.weakReference;
164
+ var signal = signalWeakRef.deref();
165
+ if (!signal) {
166
+ return; // Signal already GC'd
167
+ }
168
+ var registration = signalThrottleds.get(signal);
169
+ if (registration) {
170
+ registration.throttleds.delete(weakReference);
171
+ if (registration.throttleds.size === 0) {
172
+ // Remove the abort listener when no throttleds remain
173
+ signal.removeEventListener("abort", registration.listener);
174
+ signalThrottleds.delete(signal);
175
+ }
281
176
  }
282
- return AbortError;
283
- }(_wrapNativeSuper(Error));
177
+ });
284
178
  function pThrottle(param) {
285
- var limit = param.limit, interval = param.interval, strict = param.strict, onDelay = param.onDelay;
286
- var windowedDelay = function windowedDelay() {
179
+ var limit = param.limit, interval = param.interval, strict = param.strict, signal = param.signal, onDelay = param.onDelay, weight = param.weight;
180
+ var windowedDelay = function windowedDelay(requestWeight) {
287
181
  var now = Date.now();
288
- if (now - currentTick > interval) {
289
- activeCount = 1;
290
- currentTick = now;
182
+ if (now - state.currentTick > interval) {
183
+ state.activeWeight = requestWeight;
184
+ state.currentTick = now;
291
185
  return 0;
292
186
  }
293
- if (activeCount < limit) {
294
- activeCount++;
187
+ if (state.activeWeight + requestWeight <= limit) {
188
+ state.activeWeight += requestWeight;
295
189
  } else {
296
- currentTick += interval;
297
- activeCount = 1;
190
+ state.currentTick += interval;
191
+ state.activeWeight = requestWeight;
298
192
  }
299
- return currentTick - now;
193
+ return state.currentTick - now;
300
194
  };
301
- var strictDelay = function strictDelay() {
195
+ var strictDelay = function strictDelay(requestWeight) {
302
196
  var now = Date.now();
303
197
  // Clear the queue if there's a significant delay since the last execution
304
- if (strictTicks.length > 0 && now - strictTicks.at(-1) > interval) {
305
- strictTicks.length = 0;
198
+ if (state.strictTicks.length > 0 && now - state.strictTicks.at(-1).time > interval) {
199
+ state.strictTicks.length = 0;
306
200
  }
307
- // If the queue is not full, add the current time and execute immediately
308
- if (strictTicks.length < limit) {
309
- strictTicks.push(now);
310
- return 0;
201
+ // For weighted throttling, use time-based sliding window
202
+ if (weight) {
203
+ // Remove ticks outside the current interval window
204
+ while(state.strictTicks.length > 0 && now - state.strictTicks[0].time >= interval){
205
+ state.strictTicks.shift();
206
+ }
207
+ var weightInWindowAt = function(time) {
208
+ var total = 0;
209
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
210
+ try {
211
+ for(var _iterator = state.strictTicks[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
212
+ var tick = _step.value;
213
+ if (tick.time <= time && time - tick.time < interval) {
214
+ total += tick.weight;
215
+ }
216
+ }
217
+ } catch (err) {
218
+ _didIteratorError = true;
219
+ _iteratorError = err;
220
+ } finally{
221
+ try {
222
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
223
+ _iterator.return();
224
+ }
225
+ } finally{
226
+ if (_didIteratorError) {
227
+ throw _iteratorError;
228
+ }
229
+ }
230
+ }
231
+ return total;
232
+ };
233
+ // Execute immediately if capacity available
234
+ if (weightInWindowAt(now) + requestWeight <= limit) {
235
+ var tickRecord = {
236
+ time: now,
237
+ weight: requestWeight
238
+ };
239
+ insertTickSorted(tickRecord);
240
+ return {
241
+ delay: 0
242
+ };
243
+ }
244
+ // Find earliest time when window will have room
245
+ var nextExecutionTime = now;
246
+ while(weightInWindowAt(nextExecutionTime) + requestWeight > limit){
247
+ var firstInWindow = state.strictTicks.find(function(tick) {
248
+ return tick.time <= nextExecutionTime && nextExecutionTime - tick.time < interval;
249
+ });
250
+ if (!firstInWindow) {
251
+ break;
252
+ }
253
+ nextExecutionTime = firstInWindow.time + interval;
254
+ }
255
+ var tickRecord1 = {
256
+ time: nextExecutionTime,
257
+ weight: requestWeight
258
+ };
259
+ insertTickSorted(tickRecord1);
260
+ return {
261
+ delay: Math.max(0, nextExecutionTime - now),
262
+ tickRecord: tickRecord1
263
+ };
311
264
  }
312
- // Calculate the next execution time based on the first item in the queue
313
- var nextExecutionTime = strictTicks[0] + interval;
314
- // Shift the queue and add the new execution time
315
- strictTicks.shift();
316
- strictTicks.push(nextExecutionTime);
265
+ // For non-weighted throttling, use count-based queue (original algorithm)
266
+ // If the queue is not full (treat limit 0 as capacity 1 for seeding), add the current time and execute immediately
267
+ if (state.strictTicks.length < strictCapacity) {
268
+ state.strictTicks.push({
269
+ time: now,
270
+ weight: requestWeight
271
+ });
272
+ return {
273
+ delay: 0
274
+ };
275
+ }
276
+ // Calculate next execution time: must be after oldest + interval,
277
+ // AND must be after the most recent to prevent multiple calls bunching up
278
+ var oldestTime = state.strictTicks[0].time;
279
+ var mostRecentTime = state.strictTicks.at(-1).time;
280
+ var baseTime = oldestTime + interval;
281
+ // Add minimum spacing to prevent bunching (except for interval=0)
282
+ var minSpacing = interval > 0 ? Math.ceil(interval / strictCapacity) : 0;
283
+ var nextExecutionTime1 = baseTime <= mostRecentTime ? mostRecentTime + minSpacing : baseTime;
284
+ // Shift the queue and add a record for the new execution
285
+ state.strictTicks.shift();
286
+ var tickRecord2 = {
287
+ time: nextExecutionTime1,
288
+ weight: requestWeight
289
+ };
290
+ state.strictTicks.push(tickRecord2);
317
291
  // Calculate the delay for the current execution
318
- return Math.max(0, nextExecutionTime - now);
292
+ return {
293
+ delay: Math.max(0, nextExecutionTime1 - now),
294
+ tickRecord: tickRecord2
295
+ };
319
296
  };
320
297
  if (!Number.isFinite(limit)) {
321
298
  throw new TypeError("Expected `limit` to be a finite number");
@@ -323,10 +300,46 @@ function pThrottle(param) {
323
300
  if (!Number.isFinite(interval)) {
324
301
  throw new TypeError("Expected `interval` to be a finite number");
325
302
  }
326
- var queue = new Map();
327
- var currentTick = 0;
328
- var activeCount = 0;
329
- var strictTicks = [];
303
+ if (limit < 0) {
304
+ throw new TypeError("Expected `limit` to be >= 0");
305
+ }
306
+ if (interval < 0) {
307
+ throw new TypeError("Expected `interval` to be >= 0");
308
+ }
309
+ if (weight !== undefined && typeof weight !== "function") {
310
+ throw new TypeError("Expected `weight` to be a function");
311
+ }
312
+ if (weight && interval === 0) {
313
+ throw new TypeError("The `weight` option cannot be used with `interval` of 0");
314
+ }
315
+ // TODO: Uncomment in next major version (breaking change)
316
+ // The combination of strict mode with interval=0 doesn't enforce the limit correctly
317
+ // (minSpacing becomes 0, allowing unlimited calls in the same millisecond).
318
+ // For now, we allow it to avoid breaking existing code, but it should be rejected in v9.
319
+ // if (strict && interval === 0) {
320
+ // throw new TypeError('The `strict` option cannot be used with `interval` of 0');
321
+ // }
322
+ var state = {
323
+ queue: new Map(),
324
+ strictTicks: [],
325
+ // Track windowed algorithm state so it can be reset on abort
326
+ currentTick: 0,
327
+ activeWeight: 0
328
+ };
329
+ var strictCapacity = Math.max(limit, 1);
330
+ // Helper: insert tick maintaining sorted order by time (for weighted strict mode)
331
+ var insertTickSorted = function(tickRecord) {
332
+ // Optimization: append if it belongs at the end (common case - O(1))
333
+ if (state.strictTicks.length === 0 || tickRecord.time >= state.strictTicks.at(-1).time) {
334
+ state.strictTicks.push(tickRecord);
335
+ } else {
336
+ // Insert at correct position (rare case - O(n))
337
+ var insertIndex = state.strictTicks.findIndex(function(tick) {
338
+ return tick.time > tickRecord.time;
339
+ });
340
+ state.strictTicks.splice(insertIndex, 0, tickRecord);
341
+ }
342
+ };
330
343
  var getDelay = strict ? strictDelay : windowedDelay;
331
344
  return function(function_) {
332
345
  var throttled = function() {
@@ -347,51 +360,143 @@ function pThrottle(param) {
347
360
  }
348
361
  var timeoutId;
349
362
  return new Promise(function(resolve, reject) {
363
+ // Calculate weight for this call
364
+ var requestWeight = 1;
365
+ if (weight) {
366
+ try {
367
+ requestWeight = weight.apply(void 0, _toConsumableArray(arguments_));
368
+ } catch (error) {
369
+ reject(error);
370
+ return;
371
+ }
372
+ // Validate weight
373
+ if (!Number.isFinite(requestWeight) || requestWeight < 0) {
374
+ reject(new TypeError("Expected `weight` to be a finite non-negative number"));
375
+ return;
376
+ }
377
+ if (requestWeight > limit) {
378
+ reject(new TypeError("Expected `weight` (".concat(requestWeight, ") to be <= `limit` (").concat(limit, ")")));
379
+ return;
380
+ }
381
+ }
382
+ var delayResult = getDelay(requestWeight);
383
+ var delay = strict ? delayResult.delay : delayResult;
384
+ var tickRecord = strict ? delayResult.tickRecord : undefined;
350
385
  var execute = function() {
351
- resolve(function_.apply(_this, arguments_));
352
- queue.delete(timeoutId);
386
+ // Update strictTicks with actual execution time to account for setTimeout drift
387
+ if (tickRecord) {
388
+ var actualTime = Date.now();
389
+ // For weighted throttling with drift, maintain sorted order
390
+ if (weight && tickRecord.time !== actualTime) {
391
+ tickRecord.time = actualTime;
392
+ var index = state.strictTicks.indexOf(tickRecord);
393
+ state.strictTicks.splice(index, 1);
394
+ insertTickSorted(tickRecord);
395
+ } else {
396
+ tickRecord.time = actualTime;
397
+ }
398
+ }
399
+ try {
400
+ resolve(function_.apply(_this, arguments_));
401
+ } catch (error) {
402
+ reject(error);
403
+ }
404
+ state.queue.delete(timeoutId);
353
405
  };
354
- var delay = getDelay();
355
406
  if (delay > 0) {
356
407
  timeoutId = setTimeout(execute, delay);
357
- queue.set(timeoutId, reject);
358
- onDelay === null || onDelay === void 0 ? void 0 : onDelay();
408
+ state.queue.set(timeoutId, reject);
409
+ try {
410
+ onDelay === null || onDelay === void 0 ? void 0 : onDelay.apply(void 0, _toConsumableArray(arguments_));
411
+ } catch (e) {}
359
412
  } else {
360
413
  execute();
361
414
  }
362
415
  });
363
416
  };
364
- throttled.abort = function() {
365
- var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
366
- try {
367
- for(var _iterator = queue.keys()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
368
- var timeout = _step.value;
369
- clearTimeout(timeout);
370
- queue.get(timeout)(new AbortError());
371
- }
372
- } catch (err) {
373
- _didIteratorError = true;
374
- _iteratorError = err;
375
- } finally{
376
- try {
377
- if (!_iteratorNormalCompletion && _iterator.return != null) {
378
- _iterator.return();
379
- }
380
- } finally{
381
- if (_didIteratorError) {
382
- throw _iteratorError;
417
+ signal === null || signal === void 0 ? void 0 : signal.throwIfAborted();
418
+ if (signal) {
419
+ var registration = signalThrottleds.get(signal);
420
+ if (!registration) {
421
+ registration = {
422
+ throttleds: new Set(),
423
+ listener: null
424
+ };
425
+ registration.listener = function() {
426
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
427
+ try {
428
+ for(var _iterator = registration.throttleds[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
429
+ var weakReference = _step.value;
430
+ var function_ = weakReference.deref();
431
+ if (!function_) {
432
+ continue;
433
+ }
434
+ var functionState = states.get(function_);
435
+ if (!functionState) {
436
+ continue;
437
+ }
438
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
439
+ try {
440
+ for(var _iterator1 = functionState.queue.keys()[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
441
+ var timeout = _step1.value;
442
+ clearTimeout(timeout);
443
+ functionState.queue.get(timeout)(signal.reason);
444
+ }
445
+ } catch (err) {
446
+ _didIteratorError1 = true;
447
+ _iteratorError1 = err;
448
+ } finally{
449
+ try {
450
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
451
+ _iterator1.return();
452
+ }
453
+ } finally{
454
+ if (_didIteratorError1) {
455
+ throw _iteratorError1;
456
+ }
457
+ }
458
+ }
459
+ functionState.queue.clear();
460
+ functionState.strictTicks.length = 0;
461
+ // Reset windowed state so subsequent calls are not artificially delayed
462
+ functionState.currentTick = 0;
463
+ functionState.activeWeight = 0;
464
+ }
465
+ } catch (err) {
466
+ _didIteratorError = true;
467
+ _iteratorError = err;
468
+ } finally{
469
+ try {
470
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
471
+ _iterator.return();
472
+ }
473
+ } finally{
474
+ if (_didIteratorError) {
475
+ throw _iteratorError;
476
+ }
477
+ }
383
478
  }
384
- }
479
+ signalThrottleds.delete(signal);
480
+ };
481
+ signalThrottleds.set(signal, registration);
482
+ signal.addEventListener("abort", registration.listener, {
483
+ once: true
484
+ });
385
485
  }
386
- queue.clear();
387
- strictTicks.splice(0, strictTicks.length);
388
- };
486
+ var weakReference = new WeakRef(throttled);
487
+ registration.throttleds.add(weakReference);
488
+ finalizationRegistry.register(throttled, {
489
+ signalWeakRef: new WeakRef(signal),
490
+ weakReference: weakReference
491
+ });
492
+ }
389
493
  throttled.isEnabled = true;
390
494
  Object.defineProperty(throttled, "queueSize", {
391
495
  get: function get() {
392
- return queue.size;
496
+ return state.queue.size;
393
497
  }
394
498
  });
499
+ states.set(throttled, state);
395
500
  return throttled;
396
501
  };
397
502
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@common.js/p-throttle",
3
- "version": "6.1.0",
3
+ "version": "8.1.0",
4
4
  "description": "p-throttle package exported as CommonJS modules",
5
5
  "license": "MIT",
6
6
  "repository": "etienne-martin/common.js",
@@ -8,28 +8,30 @@
8
8
  "type": "commonjs",
9
9
  "sideEffects": false,
10
10
  "engines": {
11
- "node": ">=18"
11
+ "node": ">=20"
12
12
  },
13
13
  "scripts": {
14
- "test": "xo && ava && tsd"
14
+ "test": "xo && ava && tsd",
15
+ "test-gc": "node --expose-gc gc-test"
15
16
  },
16
17
  "files": [
17
18
  "index.js",
18
19
  "index.d.ts"
19
20
  ],
20
21
  "devDependencies": {
21
- "ava": "^5.3.1",
22
+ "ava": "^6.2.0",
22
23
  "delay": "^6.0.0",
23
24
  "in-range": "^3.0.0",
25
+ "pretty-bytes": "^7.0.1",
24
26
  "time-span": "^5.1.0",
25
- "tsd": "^0.29.0",
26
- "xo": "^0.56.0"
27
+ "tsd": "^0.31.2",
28
+ "xo": "^0.59.3"
27
29
  },
28
30
  "ava": {
29
31
  "serial": true
30
32
  },
31
33
  "homepage": "https://github.com/etienne-martin/common.js#readme",
32
34
  "dependencies": {},
33
- "types": "./index.d.ts",
34
- "main": "./index.js"
35
+ "main": "./index.js",
36
+ "types": "./index.d.ts"
35
37
  }