@common.js/p-throttle 6.2.0 → 8.1.1

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 +91 -18
  3. package/index.js +287 -207
  4. package/package.json +22 -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.2.0](https://www.npmjs.com/package/p-throttle/v/6.2.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,16 +46,46 @@ 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`. The delayed call arguments are passed to the `onDelay` callback.
56
+ Abort pending executions. When aborted, all unresolved promises are rejected with `signal.reason`.
57
+
58
+ @example
59
+ ```
60
+ import pThrottle from 'p-throttle';
61
+
62
+ const controller = new AbortController();
48
63
 
49
- Can be useful for monitoring the throttling efficiency.
64
+ const throttle = pThrottle({
65
+ limit: 2,
66
+ interval: 1000,
67
+ signal: controller.signal
68
+ });
69
+
70
+ const throttled = throttle(() => {
71
+ console.log('Executing...');
72
+ });
73
+
74
+ await throttled();
75
+ await throttled();
76
+ controller.abort('aborted');
77
+ await throttled();
78
+ //=> Executing...
79
+ //=> 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.
50
89
 
51
90
  @example
52
91
  ```
@@ -61,8 +100,8 @@ export type Options = {
61
100
  });
62
101
 
63
102
  const throttled = throttle((a, b) => {
64
- console.log(`Executing with ${a} ${b}...`);
65
- });
103
+ console.log(`Executing with ${a} ${b}...`);
104
+ });
66
105
 
67
106
  await throttled(1, 2);
68
107
  await throttled(3, 4);
@@ -74,10 +113,44 @@ export type Options = {
74
113
  ```
75
114
  */
76
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
145
+ ```
146
+ */
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,17 +2,9 @@
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
  });
@@ -24,12 +16,6 @@ function _arrayLikeToArray(arr, len) {
24
16
  function _arrayWithoutHoles(arr) {
25
17
  if (Array.isArray(arr)) return _arrayLikeToArray(arr);
26
18
  }
27
- function _assertThisInitialized(self) {
28
- if (self === void 0) {
29
- throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
30
- }
31
- return self;
32
- }
33
19
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
34
20
  try {
35
21
  var info = gen[key](arg);
@@ -59,87 +45,15 @@ function _asyncToGenerator(fn) {
59
45
  });
60
46
  };
61
47
  }
62
- function _classCallCheck(instance, Constructor) {
63
- if (!(instance instanceof Constructor)) {
64
- throw new TypeError("Cannot call a class as a function");
65
- }
66
- }
67
- function isNativeReflectConstruct() {
68
- if (typeof Reflect === "undefined" || !Reflect.construct) return false;
69
- if (Reflect.construct.sham) return false;
70
- if (typeof Proxy === "function") return true;
71
- try {
72
- Date.prototype.toString.call(Reflect.construct(Date, [], function() {}));
73
- return true;
74
- } catch (e) {
75
- return false;
76
- }
77
- }
78
- function _construct(Parent, args, Class) {
79
- if (isNativeReflectConstruct()) {
80
- _construct = Reflect.construct;
81
- } else {
82
- _construct = function _construct(Parent, args, Class) {
83
- var a = [
84
- null
85
- ];
86
- a.push.apply(a, args);
87
- var Constructor = Function.bind.apply(Parent, a);
88
- var instance = new Constructor();
89
- if (Class) _setPrototypeOf(instance, Class.prototype);
90
- return instance;
91
- };
92
- }
93
- return _construct.apply(null, arguments);
94
- }
95
- function _getPrototypeOf(o) {
96
- _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
97
- return o.__proto__ || Object.getPrototypeOf(o);
98
- };
99
- return _getPrototypeOf(o);
100
- }
101
- function _inherits(subClass, superClass) {
102
- if (typeof superClass !== "function" && superClass !== null) {
103
- throw new TypeError("Super expression must either be null or a function");
104
- }
105
- subClass.prototype = Object.create(superClass && superClass.prototype, {
106
- constructor: {
107
- value: subClass,
108
- writable: true,
109
- configurable: true
110
- }
111
- });
112
- if (superClass) _setPrototypeOf(subClass, superClass);
113
- }
114
- function _isNativeFunction(fn) {
115
- return Function.toString.call(fn).indexOf("[native code]") !== -1;
116
- }
117
48
  function _iterableToArray(iter) {
118
49
  if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
119
50
  }
120
51
  function _nonIterableSpread() {
121
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.");
122
53
  }
123
- function _possibleConstructorReturn(self, call) {
124
- if (call && (_typeof(call) === "object" || typeof call === "function")) {
125
- return call;
126
- }
127
- return _assertThisInitialized(self);
128
- }
129
- function _setPrototypeOf(o, p) {
130
- _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
131
- o.__proto__ = p;
132
- return o;
133
- };
134
- return _setPrototypeOf(o, p);
135
- }
136
54
  function _toConsumableArray(arr) {
137
55
  return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
138
56
  }
139
- var _typeof = function(obj) {
140
- "@swc/helpers - typeof";
141
- return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
142
- };
143
57
  function _unsupportedIterableToArray(o, minLen) {
144
58
  if (!o) return;
145
59
  if (typeof o === "string") return _arrayLikeToArray(o, minLen);
@@ -148,56 +62,6 @@ function _unsupportedIterableToArray(o, minLen) {
148
62
  if (n === "Map" || n === "Set") return Array.from(n);
149
63
  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
150
64
  }
151
- function _wrapNativeSuper(Class) {
152
- var _cache = typeof Map === "function" ? new Map() : undefined;
153
- _wrapNativeSuper = function _wrapNativeSuper(Class) {
154
- if (Class === null || !_isNativeFunction(Class)) return Class;
155
- if (typeof Class !== "function") {
156
- throw new TypeError("Super expression must either be null or a function");
157
- }
158
- if (typeof _cache !== "undefined") {
159
- if (_cache.has(Class)) return _cache.get(Class);
160
- _cache.set(Class, Wrapper);
161
- }
162
- function Wrapper() {
163
- return _construct(Class, arguments, _getPrototypeOf(this).constructor);
164
- }
165
- Wrapper.prototype = Object.create(Class.prototype, {
166
- constructor: {
167
- value: Wrapper,
168
- enumerable: false,
169
- writable: true,
170
- configurable: true
171
- }
172
- });
173
- return _setPrototypeOf(Wrapper, Class);
174
- };
175
- return _wrapNativeSuper(Class);
176
- }
177
- function _isNativeReflectConstruct() {
178
- if (typeof Reflect === "undefined" || !Reflect.construct) return false;
179
- if (Reflect.construct.sham) return false;
180
- if (typeof Proxy === "function") return true;
181
- try {
182
- Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function() {}));
183
- return true;
184
- } catch (e) {
185
- return false;
186
- }
187
- }
188
- function _createSuper(Derived) {
189
- var hasNativeReflectConstruct = _isNativeReflectConstruct();
190
- return function _createSuperInternal() {
191
- var Super = _getPrototypeOf(Derived), result;
192
- if (hasNativeReflectConstruct) {
193
- var NewTarget = _getPrototypeOf(this).constructor;
194
- result = Reflect.construct(Super, arguments, NewTarget);
195
- } else {
196
- result = Super.apply(this, arguments);
197
- }
198
- return _possibleConstructorReturn(this, result);
199
- };
200
- }
201
65
  var __generator = (void 0) && (void 0).__generator || function(thisArg, body) {
202
66
  var f, y, t, g, _ = {
203
67
  label: 0,
@@ -293,54 +157,142 @@ var __generator = (void 0) && (void 0).__generator || function(thisArg, body) {
293
157
  };
294
158
  }
295
159
  };
296
- var AbortError = /*#__PURE__*/ function(Error1) {
297
- "use strict";
298
- _inherits(AbortError, Error1);
299
- var _super = _createSuper(AbortError);
300
- function AbortError() {
301
- _classCallCheck(this, AbortError);
302
- var _this;
303
- _this = _super.call(this, "Throttled function aborted");
304
- _this.name = "AbortError";
305
- 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
+ }
306
176
  }
307
- return AbortError;
308
- }(_wrapNativeSuper(Error));
177
+ });
309
178
  function pThrottle(param) {
310
- var limit = param.limit, interval = param.interval, strict = param.strict, onDelay = param.onDelay;
311
- 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) {
312
181
  var now = Date.now();
313
- if (now - currentTick > interval) {
314
- activeCount = 1;
315
- currentTick = now;
182
+ if (now - state.currentTick > interval) {
183
+ state.activeWeight = requestWeight;
184
+ state.currentTick = now;
316
185
  return 0;
317
186
  }
318
- if (activeCount < limit) {
319
- activeCount++;
187
+ if (state.activeWeight + requestWeight <= limit) {
188
+ state.activeWeight += requestWeight;
320
189
  } else {
321
- currentTick += interval;
322
- activeCount = 1;
190
+ state.currentTick += interval;
191
+ state.activeWeight = requestWeight;
323
192
  }
324
- return currentTick - now;
193
+ return state.currentTick - now;
325
194
  };
326
- var strictDelay = function strictDelay() {
195
+ var strictDelay = function strictDelay(requestWeight) {
327
196
  var now = Date.now();
328
197
  // Clear the queue if there's a significant delay since the last execution
329
- if (strictTicks.length > 0 && now - strictTicks.at(-1) > interval) {
330
- strictTicks.length = 0;
198
+ if (state.strictTicks.length > 0 && now - state.strictTicks.at(-1).time > interval) {
199
+ state.strictTicks.length = 0;
331
200
  }
332
- // If the queue is not full, add the current time and execute immediately
333
- if (strictTicks.length < limit) {
334
- strictTicks.push(now);
335
- 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
+ };
264
+ }
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
+ };
336
275
  }
337
- // Calculate the next execution time based on the first item in the queue
338
- var nextExecutionTime = strictTicks[0] + interval;
339
- // Shift the queue and add the new execution time
340
- strictTicks.shift();
341
- strictTicks.push(nextExecutionTime);
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);
342
291
  // Calculate the delay for the current execution
343
- return Math.max(0, nextExecutionTime - now);
292
+ return {
293
+ delay: Math.max(0, nextExecutionTime1 - now),
294
+ tickRecord: tickRecord2
295
+ };
344
296
  };
345
297
  if (!Number.isFinite(limit)) {
346
298
  throw new TypeError("Expected `limit` to be a finite number");
@@ -348,10 +300,46 @@ function pThrottle(param) {
348
300
  if (!Number.isFinite(interval)) {
349
301
  throw new TypeError("Expected `interval` to be a finite number");
350
302
  }
351
- var queue = new Map();
352
- var currentTick = 0;
353
- var activeCount = 0;
354
- 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
+ };
355
343
  var getDelay = strict ? strictDelay : windowedDelay;
356
344
  return function(function_) {
357
345
  var throttled = function() {
@@ -372,51 +360,143 @@ function pThrottle(param) {
372
360
  }
373
361
  var timeoutId;
374
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;
375
385
  var execute = function() {
376
- resolve(function_.apply(_this, arguments_));
377
- 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);
378
405
  };
379
- var delay = getDelay();
380
406
  if (delay > 0) {
381
407
  timeoutId = setTimeout(execute, delay);
382
- queue.set(timeoutId, reject);
383
- onDelay === null || onDelay === void 0 ? void 0 : onDelay.apply(void 0, _toConsumableArray(arguments_));
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) {}
384
412
  } else {
385
413
  execute();
386
414
  }
387
415
  });
388
416
  };
389
- throttled.abort = function() {
390
- var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
391
- try {
392
- for(var _iterator = queue.keys()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
393
- var timeout = _step.value;
394
- clearTimeout(timeout);
395
- queue.get(timeout)(new AbortError());
396
- }
397
- } catch (err) {
398
- _didIteratorError = true;
399
- _iteratorError = err;
400
- } finally{
401
- try {
402
- if (!_iteratorNormalCompletion && _iterator.return != null) {
403
- _iterator.return();
404
- }
405
- } finally{
406
- if (_didIteratorError) {
407
- 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
+ }
408
478
  }
409
- }
479
+ signalThrottleds.delete(signal);
480
+ };
481
+ signalThrottleds.set(signal, registration);
482
+ signal.addEventListener("abort", registration.listener, {
483
+ once: true
484
+ });
410
485
  }
411
- queue.clear();
412
- strictTicks.splice(0, strictTicks.length);
413
- };
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
+ }
414
493
  throttled.isEnabled = true;
415
494
  Object.defineProperty(throttled, "queueSize", {
416
495
  get: function get() {
417
- return queue.size;
496
+ return state.queue.size;
418
497
  }
419
498
  });
499
+ states.set(throttled, state);
420
500
  return throttled;
421
501
  };
422
502
  }
package/package.json CHANGED
@@ -1,35 +1,49 @@
1
1
  {
2
2
  "name": "@common.js/p-throttle",
3
- "version": "6.2.0",
3
+ "version": "8.1.1",
4
4
  "description": "p-throttle package exported as CommonJS modules",
5
5
  "license": "MIT",
6
6
  "repository": "etienne-martin/common.js",
7
7
  "funding": "https://github.com/sponsors/sindresorhus",
8
8
  "type": "commonjs",
9
+ "exports": {
10
+ "types": "./index.d.ts",
11
+ "default": "./index.js"
12
+ },
9
13
  "sideEffects": false,
10
14
  "engines": {
11
- "node": ">=18"
15
+ "node": ">=20"
12
16
  },
13
17
  "scripts": {
14
- "test": "xo && ava && tsd"
18
+ "test": "xo && ava && tsd",
19
+ "test-gc": "node --expose-gc gc-test"
15
20
  },
16
21
  "files": [
17
22
  "index.js",
18
23
  "index.d.ts"
19
24
  ],
20
25
  "devDependencies": {
21
- "ava": "^5.3.1",
26
+ "ava": "^6.2.0",
22
27
  "delay": "^6.0.0",
23
28
  "in-range": "^3.0.0",
29
+ "pretty-bytes": "^7.0.1",
24
30
  "time-span": "^5.1.0",
25
- "tsd": "^0.29.0",
26
- "xo": "^0.56.0"
31
+ "tsd": "^0.31.2",
32
+ "xo": "^0.59.3"
27
33
  },
28
34
  "ava": {
29
35
  "serial": true
30
36
  },
31
37
  "homepage": "https://github.com/etienne-martin/common.js#readme",
32
38
  "dependencies": {},
33
- "types": "./index.d.ts",
34
- "main": "./index.js"
39
+ "commonjs": {
40
+ "source": {
41
+ "name": "p-throttle",
42
+ "version": "8.1.0"
43
+ },
44
+ "transformRevision": 1,
45
+ "buildKey": "dc9fd4993625694705f5a23ebe01a14ec40389d3f7a81ad39f621e0f9b38e09b"
46
+ },
47
+ "main": "./index.js",
48
+ "types": "./index.d.ts"
35
49
  }