@common.js/p-debounce 4.0.0 → 5.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 +65 -24
  3. package/index.js +324 -33
  4. package/package.json +9 -10
package/README.md CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  The [p-debounce](https://www.npmjs.com/package/p-debounce) package exported as CommonJS modules.
4
4
 
5
- Exported from [p-debounce@4.0.0](https://www.npmjs.com/package/p-debounce/v/4.0.0) using https://github.com/etienne-martin/common.js.
5
+ Exported from [p-debounce@5.1.0](https://www.npmjs.com/package/p-debounce/v/5.1.0) using https://github.com/etienne-martin/common.js.
package/index.d.ts CHANGED
@@ -1,13 +1,49 @@
1
- declare namespace pDebounce {
2
- interface Options {
3
- /**
4
- Call the `fn` on the [leading edge of the timeout](https://css-tricks.com/debouncing-throttling-explained-examples/#article-header-id-1). Meaning immediately, instead of waiting for `wait` milliseconds.
5
-
6
- @default false
7
- */
8
- readonly before?: boolean;
9
- }
10
- }
1
+ export type Options = {
2
+ /**
3
+ Call the `fn` on the [leading edge of the timeout](https://css-tricks.com/debouncing-throttling-explained-examples/#article-header-id-1). Meaning immediately, instead of waiting for `wait` milliseconds.
4
+
5
+ @default false
6
+ */
7
+ readonly before?: boolean;
8
+
9
+ /**
10
+ An `AbortSignal` to cancel the debounced function.
11
+ */
12
+ readonly signal?: AbortSignal;
13
+ };
14
+
15
+ export type PromiseOptions = {
16
+ /**
17
+ If a call is made while a previous call is still running, queue the latest arguments and run the function again after the current execution completes.
18
+
19
+ @default false
20
+
21
+ Use cases:
22
+ - With `after: false` (default): API fetches, data loading, read operations - concurrent calls share the same result.
23
+ - With `after: true`: Saving data, file writes, state updates - ensures latest data is never lost.
24
+
25
+ @example
26
+ ```
27
+ import {setTimeout as delay} from 'timers/promises';
28
+ import pDebounce from 'p-debounce';
29
+
30
+ const save = async data => {
31
+ await delay(200);
32
+ console.log(`Saved: ${data}`);
33
+ return data;
34
+ };
35
+
36
+ const debouncedSave = pDebounce.promise(save, {after: true});
37
+
38
+ // If data changes while saving, it will save again with the latest data
39
+ debouncedSave('data1');
40
+ debouncedSave('data2'); // This will run after the first save completes
41
+ //=> Saved: data1
42
+ //=> Saved: data2
43
+ ```
44
+ */
45
+ readonly after?: boolean;
46
+ };
11
47
 
12
48
  declare const pDebounce: {
13
49
  /**
@@ -23,21 +59,23 @@ declare const pDebounce: {
23
59
 
24
60
  const expensiveCall = async input => input;
25
61
 
26
- const debouncedFn = pDebounce(expensiveCall, 200);
62
+ const debouncedFunction = pDebounce(expensiveCall, 200);
27
63
 
28
64
  for (const number of [1, 2, 3]) {
29
- console.log(await debouncedFn(number));
65
+ (async () => {
66
+ console.log(await debouncedFunction(number));
67
+ })();
30
68
  }
31
69
  //=> 3
32
70
  //=> 3
33
71
  //=> 3
34
72
  ```
35
73
  */
36
- <ArgumentsType extends unknown[], ReturnType>(
37
- fn: (...arguments: ArgumentsType) => PromiseLike<ReturnType> | ReturnType,
74
+ <This, ArgumentsType extends unknown[], ReturnType>(
75
+ fn: (this: This, ...arguments: ArgumentsType) => PromiseLike<ReturnType> | ReturnType,
38
76
  wait: number,
39
- options?: pDebounce.Options
40
- ): (...arguments: ArgumentsType) => Promise<ReturnType>;
77
+ options?: Options
78
+ ): (this: This, ...arguments: ArgumentsType) => Promise<ReturnType>;
41
79
 
42
80
  /**
43
81
  Execute `function_` unless a previous call is still pending, in which case, return the pending promise. Useful, for example, to avoid processing extra button clicks if the previous one is not complete.
@@ -52,21 +90,24 @@ declare const pDebounce: {
52
90
  const expensiveCall = async value => {
53
91
  await delay(200);
54
92
  return value;
55
- }
93
+ };
56
94
 
57
- const debouncedFn = pDebounce.promise(expensiveCall);
95
+ const debouncedFunction = pDebounce.promise(expensiveCall);
58
96
 
59
97
  for (const number of [1, 2, 3]) {
60
- console.log(await debouncedFn(number));
98
+ (async () => {
99
+ console.log(await debouncedFunction(number));
100
+ })();
61
101
  }
62
102
  //=> 1
63
- //=> 2
64
- //=> 3
103
+ //=> 1
104
+ //=> 1
65
105
  ```
66
106
  */
67
- promise<ArgumentsType extends unknown[], ReturnType>(
68
- function_: (...arguments: ArgumentsType) => PromiseLike<ReturnType> | ReturnType
69
- ): (...arguments: ArgumentsType) => Promise<ReturnType>;
107
+ promise<This, ArgumentsType extends unknown[], ReturnType>(
108
+ function_: (this: This, ...arguments: ArgumentsType) => PromiseLike<ReturnType> | ReturnType,
109
+ options?: PromiseOptions
110
+ ): (this: This, ...arguments: ArgumentsType) => Promise<ReturnType>;
70
111
  };
71
112
 
72
113
  export default pDebounce;
package/index.js CHANGED
@@ -132,60 +132,219 @@ var __generator = (void 0) && (void 0).__generator || function(thisArg, body) {
132
132
  };
133
133
  }
134
134
  };
135
- var pDebounce = function(fn, wait) {
135
+ var pDebounce = function(functionToDebounce, wait) {
136
136
  var options = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {};
137
137
  if (!Number.isFinite(wait)) {
138
138
  throw new TypeError("Expected `wait` to be a finite number");
139
139
  }
140
140
  var leadingValue;
141
141
  var timeout;
142
- var resolveList = [];
142
+ var promiseHandlers = []; // Single array of {resolve, reject}
143
+ var onAbort = function() {
144
+ clearTimeout(timeout);
145
+ timeout = undefined;
146
+ try {
147
+ var ref;
148
+ (ref = options.signal) === null || ref === void 0 ? void 0 : ref.throwIfAborted();
149
+ } catch (error) {
150
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
151
+ try {
152
+ for(var _iterator = promiseHandlers[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
153
+ var reject = _step.value.reject;
154
+ reject(error);
155
+ }
156
+ } catch (err) {
157
+ _didIteratorError = true;
158
+ _iteratorError = err;
159
+ } finally{
160
+ try {
161
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
162
+ _iterator.return();
163
+ }
164
+ } finally{
165
+ if (_didIteratorError) {
166
+ throw _iteratorError;
167
+ }
168
+ }
169
+ }
170
+ promiseHandlers = [];
171
+ }
172
+ };
143
173
  return function pDebounce() {
144
174
  for(var _len = arguments.length, arguments_ = new Array(_len), _key = 0; _key < _len; _key++){
145
175
  arguments_[_key] = arguments[_key];
146
176
  }
147
177
  var _this = this;
148
- return new Promise(function(resolve) {
178
+ return new Promise(function(resolve, reject) {
179
+ // Check if already aborted
180
+ try {
181
+ var ref;
182
+ (ref = options.signal) === null || ref === void 0 ? void 0 : ref.throwIfAborted();
183
+ } catch (error) {
184
+ reject(error);
185
+ return;
186
+ }
149
187
  var shouldCallNow = options.before && !timeout;
150
188
  clearTimeout(timeout);
151
- timeout = setTimeout(function() {
152
- timeout = null;
153
- var result = options.before ? leadingValue : fn.apply(_this, arguments_);
154
- var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
155
- try {
156
- for(var _iterator = resolveList[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
157
- resolve = _step.value;
158
- resolve(result);
159
- }
160
- } catch (err) {
161
- _didIteratorError = true;
162
- _iteratorError = err;
163
- } finally{
164
- try {
165
- if (!_iteratorNormalCompletion && _iterator.return != null) {
166
- _iterator.return();
167
- }
168
- } finally{
169
- if (_didIteratorError) {
170
- throw _iteratorError;
171
- }
189
+ var _this1 = _this;
190
+ timeout = setTimeout(/*#__PURE__*/ _asyncToGenerator(function() {
191
+ var // Remove abort listener
192
+ ref, currentHandlers, result, _tmp, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, _value, resolveFunction, error, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, _value1, rejectFunction;
193
+ return __generator(this, function(_state) {
194
+ switch(_state.label){
195
+ case 0:
196
+ timeout = undefined;
197
+ currentHandlers = promiseHandlers;
198
+ // Clear handlers for next cycle (new calls during execution will add to new list)
199
+ promiseHandlers = [];
200
+ _state.label = 1;
201
+ case 1:
202
+ _state.trys.push([
203
+ 1,
204
+ 5,
205
+ ,
206
+ 6
207
+ ]);
208
+ if (!options.before) return [
209
+ 3,
210
+ 2
211
+ ];
212
+ _tmp = leadingValue;
213
+ return [
214
+ 3,
215
+ 4
216
+ ];
217
+ case 2:
218
+ return [
219
+ 4,
220
+ functionToDebounce.apply(_this1, arguments_)
221
+ ];
222
+ case 3:
223
+ _tmp = _state.sent();
224
+ _state.label = 4;
225
+ case 4:
226
+ result = _tmp;
227
+ _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
228
+ try {
229
+ for(_iterator = currentHandlers[Symbol.iterator](); !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
230
+ _value = _step.value, resolveFunction = _value.resolve;
231
+ resolveFunction(result);
232
+ }
233
+ } catch (err) {
234
+ _didIteratorError = true;
235
+ _iteratorError = err;
236
+ } finally{
237
+ try {
238
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
239
+ _iterator.return();
240
+ }
241
+ } finally{
242
+ if (_didIteratorError) {
243
+ throw _iteratorError;
244
+ }
245
+ }
246
+ }
247
+ return [
248
+ 3,
249
+ 6
250
+ ];
251
+ case 5:
252
+ error = _state.sent();
253
+ _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
254
+ try {
255
+ for(_iterator1 = currentHandlers[Symbol.iterator](); !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
256
+ _value1 = _step1.value, rejectFunction = _value1.reject;
257
+ rejectFunction(error);
258
+ }
259
+ } catch (err) {
260
+ _didIteratorError1 = true;
261
+ _iteratorError1 = err;
262
+ } finally{
263
+ try {
264
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
265
+ _iterator1.return();
266
+ }
267
+ } finally{
268
+ if (_didIteratorError1) {
269
+ throw _iteratorError1;
270
+ }
271
+ }
272
+ }
273
+ return [
274
+ 3,
275
+ 6
276
+ ];
277
+ case 6:
278
+ // Clear leading value for next cycle
279
+ leadingValue = undefined;
280
+ (ref = options.signal) === null || ref === void 0 ? void 0 : ref.removeEventListener("abort", onAbort);
281
+ return [
282
+ 2
283
+ ];
172
284
  }
173
- }
174
- resolveList = [];
175
- }, wait);
285
+ });
286
+ }), wait);
176
287
  if (shouldCallNow) {
177
- leadingValue = fn.apply(_this, arguments_);
178
- resolve(leadingValue);
288
+ var _this2 = _this;
289
+ // Execute immediately for leading edge
290
+ _asyncToGenerator(function() {
291
+ var error;
292
+ return __generator(this, function(_state) {
293
+ switch(_state.label){
294
+ case 0:
295
+ _state.trys.push([
296
+ 0,
297
+ 2,
298
+ ,
299
+ 3
300
+ ]);
301
+ return [
302
+ 4,
303
+ functionToDebounce.apply(_this2, arguments_)
304
+ ];
305
+ case 1:
306
+ leadingValue = _state.sent();
307
+ resolve(leadingValue);
308
+ return [
309
+ 3,
310
+ 3
311
+ ];
312
+ case 2:
313
+ error = _state.sent();
314
+ reject(error);
315
+ return [
316
+ 3,
317
+ 3
318
+ ];
319
+ case 3:
320
+ return [
321
+ 2
322
+ ];
323
+ }
324
+ });
325
+ })();
179
326
  } else {
180
- resolveList.push(resolve);
327
+ // Add to handlers for later resolution
328
+ promiseHandlers.push({
329
+ resolve: resolve,
330
+ reject: reject
331
+ });
332
+ // Set up abort listener (only once per batch)
333
+ if (options.signal && promiseHandlers.length === 1) {
334
+ options.signal.addEventListener("abort", onAbort, {
335
+ once: true
336
+ });
337
+ }
181
338
  }
182
339
  });
183
340
  };
184
341
  };
185
342
  pDebounce.promise = function(function_) {
343
+ var options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
186
344
  var currentPromise;
345
+ var queuedCall;
187
346
  return /*#__PURE__*/ _asyncToGenerator(function() {
188
- var _len, arguments_, _key;
347
+ var _len, arguments_, _key, _this;
189
348
  var _arguments = arguments;
190
349
  return __generator(this, function(_state) {
191
350
  switch(_state.label){
@@ -194,11 +353,144 @@ pDebounce.promise = function(function_) {
194
353
  arguments_[_key] = _arguments[_key];
195
354
  }
196
355
  if (currentPromise) {
356
+ if (!options.after) {
357
+ return [
358
+ 2,
359
+ currentPromise
360
+ ];
361
+ }
362
+ // Queue latest call (replacing any existing queue)
363
+ queuedCall !== null && queuedCall !== void 0 ? queuedCall : queuedCall = {
364
+ resolvers: []
365
+ };
366
+ queuedCall.arguments = arguments_;
367
+ queuedCall.context = this;
197
368
  return [
198
369
  2,
199
- currentPromise
370
+ new Promise(function(resolve, reject) {
371
+ queuedCall.resolvers.push({
372
+ resolve: resolve,
373
+ reject: reject
374
+ });
375
+ })
200
376
  ];
201
377
  }
378
+ _this = this;
379
+ currentPromise = _asyncToGenerator(function() {
380
+ var result, initialError, error, call, queuedResult, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, resolve, error1, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, reject;
381
+ return __generator(this, function(_state) {
382
+ switch(_state.label){
383
+ case 0:
384
+ _state.trys.push([
385
+ 0,
386
+ 2,
387
+ ,
388
+ 3
389
+ ]);
390
+ return [
391
+ 4,
392
+ function_.apply(_this, arguments_)
393
+ ];
394
+ case 1:
395
+ result = _state.sent();
396
+ return [
397
+ 3,
398
+ 3
399
+ ];
400
+ case 2:
401
+ error = _state.sent();
402
+ initialError = error;
403
+ return [
404
+ 3,
405
+ 3
406
+ ];
407
+ case 3:
408
+ if (!queuedCall) return [
409
+ 3,
410
+ 8
411
+ ];
412
+ call = queuedCall;
413
+ queuedCall = undefined;
414
+ _state.label = 4;
415
+ case 4:
416
+ _state.trys.push([
417
+ 4,
418
+ 6,
419
+ ,
420
+ 7
421
+ ]);
422
+ return [
423
+ 4,
424
+ function_.apply(call.context, call.arguments)
425
+ ];
426
+ case 5:
427
+ queuedResult = _state.sent();
428
+ _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
429
+ try {
430
+ for(_iterator = call.resolvers[Symbol.iterator](); !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
431
+ resolve = _step.value.resolve;
432
+ resolve(queuedResult);
433
+ }
434
+ } catch (err) {
435
+ _didIteratorError = true;
436
+ _iteratorError = err;
437
+ } finally{
438
+ try {
439
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
440
+ _iterator.return();
441
+ }
442
+ } finally{
443
+ if (_didIteratorError) {
444
+ throw _iteratorError;
445
+ }
446
+ }
447
+ }
448
+ return [
449
+ 3,
450
+ 7
451
+ ];
452
+ case 6:
453
+ error1 = _state.sent();
454
+ _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
455
+ try {
456
+ for(_iterator1 = call.resolvers[Symbol.iterator](); !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
457
+ reject = _step1.value.reject;
458
+ reject(error1);
459
+ }
460
+ } catch (err) {
461
+ _didIteratorError1 = true;
462
+ _iteratorError1 = err;
463
+ } finally{
464
+ try {
465
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
466
+ _iterator1.return();
467
+ }
468
+ } finally{
469
+ if (_didIteratorError1) {
470
+ throw _iteratorError1;
471
+ }
472
+ }
473
+ }
474
+ return [
475
+ 3,
476
+ 7
477
+ ];
478
+ case 7:
479
+ return [
480
+ 3,
481
+ 3
482
+ ];
483
+ case 8:
484
+ if (initialError) {
485
+ throw initialError;
486
+ }
487
+ return [
488
+ 2,
489
+ result
490
+ ];
491
+ }
492
+ });
493
+ })();
202
494
  _state.label = 1;
203
495
  case 1:
204
496
  _state.trys.push([
@@ -207,7 +499,6 @@ pDebounce.promise = function(function_) {
207
499
  3,
208
500
  4
209
501
  ]);
210
- currentPromise = function_.apply(this, arguments_);
211
502
  return [
212
503
  4,
213
504
  currentPromise
package/package.json CHANGED
@@ -1,29 +1,28 @@
1
1
  {
2
2
  "name": "@common.js/p-debounce",
3
- "version": "4.0.0",
3
+ "version": "5.1.0",
4
4
  "description": "p-debounce package exported as CommonJS modules",
5
5
  "license": "MIT",
6
6
  "repository": "etienne-martin/common.js",
7
+ "funding": "https://github.com/sponsors/sindresorhus",
7
8
  "type": "commonjs",
9
+ "sideEffects": false,
8
10
  "engines": {
9
- "node": ">=12"
11
+ "node": ">=20"
10
12
  },
11
13
  "scripts": {
12
- "test": "xo && ava && tsd"
14
+ "test": "xo && node --test && tsd"
13
15
  },
14
16
  "files": [
15
17
  "index.js",
16
18
  "index.d.ts"
17
19
  ],
18
20
  "devDependencies": {
19
- "ava": "^3.15.0",
20
- "in-range": "^2.0.0",
21
- "time-span": "^4.0.0",
22
- "tsd": "^0.14.0",
23
- "xo": "^0.37.1",
24
- "yoctodelay": "^1.1.0"
21
+ "tsd": "^0.33.0",
22
+ "xo": "^1.2.2"
25
23
  },
26
24
  "homepage": "https://github.com/etienne-martin/common.js#readme",
27
25
  "dependencies": {},
28
- "main": "./index.js"
26
+ "main": "./index.js",
27
+ "types": "./index.d.ts"
29
28
  }