perfect-debounce 0.1.3 → 2.0.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.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2022 - UnJS
3
+ Copyright (c) Pooya Parsa <pooya@pi0.io>
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,40 +1,37 @@
1
1
  # perfect-debounce
2
2
 
3
- [![npm version][npm-version-src]][npm-version-href]
4
- [![npm downloads][npm-downloads-src]][npm-downloads-href]
5
- [![Github Actions][github-actions-src]][github-actions-href]
6
- [![Codecov][codecov-src]][codecov-href]
3
+ <!-- automd:badges color=yellow codecov bundlephobia packagephobia -->
7
4
 
8
- > An improved debounce function with Promise support.
5
+ [![npm version](https://img.shields.io/npm/v/perfect-debounce?color=yellow)](https://npmjs.com/package/perfect-debounce)
6
+ [![npm downloads](https://img.shields.io/npm/dm/perfect-debounce?color=yellow)](https://npm.chart.dev/perfect-debounce)
7
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/perfect-debounce?color=yellow)](https://bundlephobia.com/package/perfect-debounce)
8
+ [![install size](https://badgen.net/packagephobia/install/perfect-debounce?color=yellow)](https://packagephobia.com/result?p=perfect-debounce)
9
+ [![codecov](https://img.shields.io/codecov/c/gh/unjs/perfect-debounce?color=yellow)](https://codecov.io/gh/unjs/perfect-debounce)
10
+
11
+ <!-- /automd -->
12
+
13
+ Improved debounce function with Promise support.
14
+
15
+ ## Features
9
16
 
10
17
  - Well tested debounce implementation
11
18
  - Native Promise support
12
19
  - Avoid duplicate calls while promise is being resolved
13
20
  - Configurable `trailing` and `leading` behavior
21
+ - Control methods
14
22
 
15
23
  ## Usage
16
24
 
17
25
  Install package:
18
26
 
19
27
  ```sh
20
- # npm
21
- npm install perfect-debounce
22
-
23
- # yarn
24
- yarn install perfect-debounce
25
-
26
- # pnpm
27
- pnpm install perfect-debounce
28
+ npx nypm i perfect-debounce
28
29
  ```
29
30
 
30
31
  Import:
31
32
 
32
33
  ```js
33
- // ESM
34
- import { debounce } from 'perfect-debounce'
35
-
36
- // CommonJS
37
- const { debounce } = require('perfect-debounce')
34
+ import { debounce } from "perfect-debounce";
38
35
  ```
39
36
 
40
37
  Debounce function:
@@ -42,25 +39,70 @@ Debounce function:
42
39
  ```js
43
40
  const debounced = debounce(async () => {
44
41
  // Some heavy stuff
45
- }, 25)
42
+ }, 25);
46
43
  ```
47
44
 
48
- When calling `debounced`, it will wait at least for `25ms` as configured before actually calling our function. This helps to avoid multiple calls.
45
+ When calling `debounced`, it will wait at least for `25ms` as configured before actually calling your function. This helps to avoid multiple calls.
46
+
47
+ ### Control Methods
48
+
49
+ The returned debounced function provides additional control methods:
50
+
51
+ - `debounced.cancel()`: Cancel any pending invocation that has not yet occurred.
52
+ - `await debounced.flush()`: Immediately invoke the pending function call (if any) and return its result.
53
+ - `debounced.isPending()`: Returns `true` if there is a pending invocation waiting to be called, otherwise `false`.
54
+
55
+ ```js
56
+ debounced.cancel(); // Cancel any pending call
57
+ await debounced.flush(); // Immediately invoke pending call (if any)
58
+ debounced.isPending(); // Returns true if a call is pending
59
+ ```
60
+
61
+ ### Example
62
+
63
+ ```js
64
+ const debounced = debounce(async (value) => {
65
+ // Some async work
66
+ return value * 2;
67
+ }, 100);
68
+
69
+ debounced(1);
70
+ debounced(2);
71
+ debounced(3);
72
+
73
+ // Check if a call is pending
74
+ console.log(debounced.isPending()); // true
75
+
76
+ // Immediately invoke the pending call
77
+ const result = await debounced.flush();
78
+ console.log(result); // 6
79
+
80
+ // Cancel any further pending calls
81
+ debounced.cancel();
82
+ ```
49
83
 
50
84
  To avoid initial wait, we can set `leading: true` option. It will cause function to be immediately called if there is no other call:
51
85
 
52
86
  ```js
53
- const debounced = debounce(async () => {
54
- // Some heavy stuff
55
- }, 25, { leading: true })
87
+ const debounced = debounce(
88
+ async () => {
89
+ // Some heavy stuff
90
+ },
91
+ 25,
92
+ { leading: true },
93
+ );
56
94
  ```
57
95
 
58
96
  If executing async function takes longer than debounce value, duplicate calls will be still prevented a last call will happen. To disable this behavior, we can set `trailing: false` option:
59
97
 
60
98
  ```js
61
- const debounced = debounce(async () => {
62
- // Some heavy stuff
63
- }, 25, { trailing: false })
99
+ const debounced = debounce(
100
+ async () => {
101
+ // Some heavy stuff
102
+ },
103
+ 25,
104
+ { trailing: false },
105
+ );
64
106
  ```
65
107
 
66
108
  ## 💻 Development
@@ -72,21 +114,6 @@ const debounced = debounce(async () => {
72
114
 
73
115
  ## License
74
116
 
75
- Made with 💛
76
-
77
117
  Based on [sindresorhus/p-debounce](https://github.com/sindresorhus/p-debounce).
78
118
 
79
- Published under [MIT License](./LICENSE).
80
-
81
- <!-- Badges -->
82
- [npm-version-src]: https://img.shields.io/npm/v/perfect-debounce?style=flat-square
83
- [npm-version-href]: https://npmjs.com/package/perfect-debounce
84
-
85
- [npm-downloads-src]: https://img.shields.io/npm/dm/perfect-debounce?style=flat-square
86
- [npm-downloads-href]: https://npmjs.com/package/perfect-debounce
87
-
88
- [github-actions-src]: https://img.shields.io/github/workflow/status/unjs/perfect-debounce/ci/main?style=flat-square
89
- [github-actions-href]: https://github.com/unjs/perfect-debounce/actions?query=workflow%3Aci
90
-
91
- [codecov-src]: https://img.shields.io/codecov/c/gh/unjs/perfect-debounce/main?style=flat-square
92
- [codecov-href]: https://codecov.io/gh/unjs/perfect-debounce
119
+ Made with 💛 Published under [MIT License](./LICENSE).
@@ -0,0 +1,49 @@
1
+ //#region src/index.d.ts
2
+ interface DebounceOptions {
3
+ /**
4
+ Call the `fn` on the [leading edge of the timeout](https://css-tricks.com/debouncing-throttling-explained-examples/#article-header-id-1).
5
+ Meaning immediately, instead of waiting for `wait` milliseconds.
6
+ @default false
7
+ */
8
+ readonly leading?: boolean;
9
+ /**
10
+ Call the `fn` on trailing edge with last used arguments. Result of call is from previous call.
11
+ @default true
12
+ */
13
+ readonly trailing?: boolean;
14
+ }
15
+ type DebouncedReturn<ArgumentsT extends unknown[], ReturnT> = ((...args: ArgumentsT) => Promise<ReturnT>) & {
16
+ /**
17
+ * Cancel pending function call
18
+ */
19
+ cancel: () => void;
20
+ /**
21
+ * Immediately invoke pending function call
22
+ */
23
+ flush: () => Promise<ReturnT> | undefined;
24
+ /**
25
+ * Get pending function call
26
+ */
27
+ isPending: () => boolean;
28
+ };
29
+ /**
30
+ Debounce functions
31
+ @param fn - Promise-returning/async function to debounce.
32
+ @param wait - Milliseconds to wait before calling `fn`. Default value is 25ms
33
+ @returns A function that delays calling `fn` until after `wait` milliseconds have elapsed since the last time it was called.
34
+ @example
35
+ ```
36
+ import { debounce } from 'perfect-debounce';
37
+ const expensiveCall = async input => input;
38
+ const debouncedFn = debounce(expensiveCall, 200);
39
+ for (const number of [1, 2, 3]) {
40
+ console.log(await debouncedFn(number));
41
+ }
42
+ //=> 1
43
+ //=> 2
44
+ //=> 3
45
+ ```
46
+ */
47
+ declare function debounce<ArgumentsT extends unknown[], ReturnT>(fn: (...args: ArgumentsT) => PromiseLike<ReturnT> | ReturnT, wait?: number, options?: DebounceOptions): DebouncedReturn<ArgumentsT, ReturnT>;
48
+ //#endregion
49
+ export { DebounceOptions, debounce };
package/dist/index.mjs CHANGED
@@ -1,57 +1,89 @@
1
- const DEBOUNCE_DEFAULTS = {
2
- trailing: true
3
- };
1
+ //#region src/index.ts
2
+ const DEBOUNCE_DEFAULTS = { trailing: true };
3
+ /**
4
+ Debounce functions
5
+ @param fn - Promise-returning/async function to debounce.
6
+ @param wait - Milliseconds to wait before calling `fn`. Default value is 25ms
7
+ @returns A function that delays calling `fn` until after `wait` milliseconds have elapsed since the last time it was called.
8
+ @example
9
+ ```
10
+ import { debounce } from 'perfect-debounce';
11
+ const expensiveCall = async input => input;
12
+ const debouncedFn = debounce(expensiveCall, 200);
13
+ for (const number of [1, 2, 3]) {
14
+ console.log(await debouncedFn(number));
15
+ }
16
+ //=> 1
17
+ //=> 2
18
+ //=> 3
19
+ ```
20
+ */
4
21
  function debounce(fn, wait = 25, options = {}) {
5
- options = { ...DEBOUNCE_DEFAULTS, ...options };
6
- if (!Number.isFinite(wait)) {
7
- throw new TypeError("Expected `wait` to be a finite number");
8
- }
9
- let leadingValue;
10
- let timeout;
11
- let resolveList = [];
12
- let currentPromise;
13
- let trailingArgs;
14
- const applyFn = async (_this, args) => {
15
- currentPromise = _applyPromised(fn, _this, args);
16
- currentPromise.finally(() => {
17
- currentPromise = null;
18
- if (options.trailing && trailingArgs && !timeout) {
19
- const promise = applyFn(_this, trailingArgs);
20
- trailingArgs = null;
21
- return promise;
22
- }
23
- });
24
- return currentPromise;
25
- };
26
- return function(...args) {
27
- if (currentPromise) {
28
- if (options.trailing) {
29
- trailingArgs = args;
30
- }
31
- return currentPromise;
32
- }
33
- return new Promise((resolve) => {
34
- const shouldCallNow = !timeout && options.leading;
35
- clearTimeout(timeout);
36
- timeout = setTimeout(() => {
37
- timeout = null;
38
- const promise = options.leading ? leadingValue : applyFn(this, args);
39
- for (const _resolve of resolveList) {
40
- _resolve(promise);
41
- }
42
- resolveList = [];
43
- }, wait);
44
- if (shouldCallNow) {
45
- leadingValue = applyFn(this, args);
46
- resolve(leadingValue);
47
- } else {
48
- resolveList.push(resolve);
49
- }
50
- });
51
- };
22
+ options = {
23
+ ...DEBOUNCE_DEFAULTS,
24
+ ...options
25
+ };
26
+ if (!Number.isFinite(wait)) throw new TypeError("Expected `wait` to be a finite number");
27
+ let leadingValue;
28
+ let timeout;
29
+ let resolveList = [];
30
+ let currentPromise;
31
+ let trailingArgs;
32
+ const applyFn = (_this, args) => {
33
+ currentPromise = _applyPromised(fn, _this, args);
34
+ currentPromise.finally(() => {
35
+ currentPromise = null;
36
+ if (options.trailing && trailingArgs && !timeout) {
37
+ const promise = applyFn(_this, trailingArgs);
38
+ trailingArgs = null;
39
+ return promise;
40
+ }
41
+ });
42
+ return currentPromise;
43
+ };
44
+ const debounced = function(...args) {
45
+ if (options.trailing) trailingArgs = args;
46
+ if (currentPromise) return currentPromise;
47
+ return new Promise((resolve) => {
48
+ const shouldCallNow = !timeout && options.leading;
49
+ clearTimeout(timeout);
50
+ timeout = setTimeout(() => {
51
+ timeout = null;
52
+ const promise = options.leading ? leadingValue : applyFn(this, args);
53
+ trailingArgs = null;
54
+ for (const _resolve of resolveList) _resolve(promise);
55
+ resolveList = [];
56
+ }, wait);
57
+ if (shouldCallNow) {
58
+ leadingValue = applyFn(this, args);
59
+ resolve(leadingValue);
60
+ } else resolveList.push(resolve);
61
+ });
62
+ };
63
+ const _clearTimeout = (timer) => {
64
+ if (timer) {
65
+ clearTimeout(timer);
66
+ timeout = null;
67
+ }
68
+ };
69
+ debounced.isPending = () => !!timeout;
70
+ debounced.cancel = () => {
71
+ _clearTimeout(timeout);
72
+ resolveList = [];
73
+ trailingArgs = null;
74
+ };
75
+ debounced.flush = () => {
76
+ _clearTimeout(timeout);
77
+ if (!trailingArgs || currentPromise) return;
78
+ const args = trailingArgs;
79
+ trailingArgs = null;
80
+ return applyFn(this, args);
81
+ };
82
+ return debounced;
52
83
  }
53
84
  async function _applyPromised(fn, _this, args) {
54
- return await fn.apply(_this, args);
85
+ return await fn.apply(_this, args);
55
86
  }
56
87
 
57
- export { debounce };
88
+ //#endregion
89
+ export { debounce };
package/package.json CHANGED
@@ -1,42 +1,41 @@
1
1
  {
2
2
  "name": "perfect-debounce",
3
- "version": "0.1.3",
3
+ "version": "2.0.0",
4
4
  "description": "",
5
5
  "repository": "unjs/perfect-debounce",
6
6
  "license": "MIT",
7
7
  "sideEffects": false,
8
8
  "type": "module",
9
9
  "exports": {
10
- ".": {
11
- "import": "./dist/index.mjs",
12
- "require": "./dist/index.cjs"
13
- }
10
+ ".": "./dist/index.mjs"
14
11
  },
15
- "main": "./dist/index.cjs",
12
+ "main": "./dist/index.mjs",
16
13
  "module": "./dist/index.mjs",
17
- "types": "./dist/index.d.ts",
14
+ "types": "./dist/index.d.mts",
18
15
  "files": [
19
16
  "dist"
20
17
  ],
21
- "dependencies": {},
22
- "devDependencies": {
23
- "@nuxtjs/eslint-config-typescript": "latest",
24
- "c8": "latest",
25
- "eslint": "latest",
26
- "in-range": "^3.0.0",
27
- "standard-version": "latest",
28
- "time-span": "^5.0.0",
29
- "typescript": "latest",
30
- "unbuild": "latest",
31
- "vitest": "latest"
32
- },
33
- "packageManager": "pnpm@6.32.3",
34
18
  "scripts": {
35
- "build": "unbuild",
19
+ "build": "obuild",
36
20
  "dev": "vitest dev",
37
- "lint": "eslint --ext .ts,.js,.mjs,.cjs .",
38
- "release": "pnpm test && standard-version && git push --follow-tags && pnpm publish",
21
+ "lint": "eslint . && prettier --check src test",
22
+ "lint:fix": "eslint . --fix && prettier -w src test",
23
+ "release": "pnpm test && pnpm build && changelogen --release --push && npm publish",
39
24
  "test": "vitest run --coverage"
40
25
  },
41
- "readme": "# perfect-debounce\n\n[![npm version][npm-version-src]][npm-version-href]\n[![npm downloads][npm-downloads-src]][npm-downloads-href]\n[![Github Actions][github-actions-src]][github-actions-href]\n[![Codecov][codecov-src]][codecov-href]\n\n> An improved debounce function with Promise support.\n\n- Well tested debounce implementation\n- Native Promise support\n- Avoid duplicate calls while promise is being resolved\n- Configurable `trailing` and `leading` behavior\n\n## Usage\n\nInstall package:\n\n```sh\n# npm\nnpm install perfect-debounce\n\n# yarn\nyarn install perfect-debounce\n\n# pnpm\npnpm install perfect-debounce\n```\n\nImport:\n\n```js\n// ESM\nimport { debounce } from 'perfect-debounce'\n\n// CommonJS\nconst { debounce } = require('perfect-debounce')\n```\n\nDebounce function:\n\n```js\nconst debounced = debounce(async () => {\n // Some heavy stuff\n}, 25)\n```\n\nWhen calling `debounced`, it will wait at least for `25ms` as configured before actually calling our function. This helps to avoid multiple calls.\n\nTo avoid initial wait, we can set `leading: true` option. It will cause function to be immediately called if there is no other call:\n\n```js\nconst debounced = debounce(async () => {\n // Some heavy stuff\n}, 25, { leading: true })\n```\n\nIf executing async function takes longer than debounce value, duplicate calls will be still prevented a last call will happen. To disable this behavior, we can set `trailing: false` option:\n\n```js\nconst debounced = debounce(async () => {\n // Some heavy stuff\n}, 25, { trailing: false })\n```\n\n## 💻 Development\n\n- Clone this repository\n- Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable` (use `npm i -g corepack` for Node.js < 16.10)\n- Install dependencies using `pnpm install`\n- Run interactive tests using `pnpm dev`\n\n## License\n\nMade with 💛\n\nBased on [sindresorhus/p-debounce](https://github.com/sindresorhus/p-debounce).\n\nPublished under [MIT License](./LICENSE).\n\n<!-- Badges -->\n[npm-version-src]: https://img.shields.io/npm/v/perfect-debounce?style=flat-square\n[npm-version-href]: https://npmjs.com/package/perfect-debounce\n\n[npm-downloads-src]: https://img.shields.io/npm/dm/perfect-debounce?style=flat-square\n[npm-downloads-href]: https://npmjs.com/package/perfect-debounce\n\n[github-actions-src]: https://img.shields.io/github/workflow/status/unjs/perfect-debounce/ci/main?style=flat-square\n[github-actions-href]: https://github.com/unjs/perfect-debounce/actions?query=workflow%3Aci\n\n[codecov-src]: https://img.shields.io/codecov/c/gh/unjs/perfect-debounce/main?style=flat-square\n[codecov-href]: https://codecov.io/gh/unjs/perfect-debounce\n"
42
- }
26
+ "devDependencies": {
27
+ "@types/node": "^24.3.0",
28
+ "@vitest/coverage-v8": "^3.2.4",
29
+ "automd": "^0.4.0",
30
+ "changelogen": "^0.6.2",
31
+ "eslint": "^9.34.0",
32
+ "eslint-config-unjs": "^0.5.0",
33
+ "in-range": "^3.0.0",
34
+ "obuild": "^0.2.1",
35
+ "prettier": "^3.6.2",
36
+ "time-span": "^5.1.0",
37
+ "typescript": "^5.9.2",
38
+ "vitest": "^3.2.4"
39
+ },
40
+ "packageManager": "pnpm@10.15.0"
41
+ }
package/dist/index.cjs DELETED
@@ -1,61 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- const DEBOUNCE_DEFAULTS = {
6
- trailing: true
7
- };
8
- function debounce(fn, wait = 25, options = {}) {
9
- options = { ...DEBOUNCE_DEFAULTS, ...options };
10
- if (!Number.isFinite(wait)) {
11
- throw new TypeError("Expected `wait` to be a finite number");
12
- }
13
- let leadingValue;
14
- let timeout;
15
- let resolveList = [];
16
- let currentPromise;
17
- let trailingArgs;
18
- const applyFn = async (_this, args) => {
19
- currentPromise = _applyPromised(fn, _this, args);
20
- currentPromise.finally(() => {
21
- currentPromise = null;
22
- if (options.trailing && trailingArgs && !timeout) {
23
- const promise = applyFn(_this, trailingArgs);
24
- trailingArgs = null;
25
- return promise;
26
- }
27
- });
28
- return currentPromise;
29
- };
30
- return function(...args) {
31
- if (currentPromise) {
32
- if (options.trailing) {
33
- trailingArgs = args;
34
- }
35
- return currentPromise;
36
- }
37
- return new Promise((resolve) => {
38
- const shouldCallNow = !timeout && options.leading;
39
- clearTimeout(timeout);
40
- timeout = setTimeout(() => {
41
- timeout = null;
42
- const promise = options.leading ? leadingValue : applyFn(this, args);
43
- for (const _resolve of resolveList) {
44
- _resolve(promise);
45
- }
46
- resolveList = [];
47
- }, wait);
48
- if (shouldCallNow) {
49
- leadingValue = applyFn(this, args);
50
- resolve(leadingValue);
51
- } else {
52
- resolveList.push(resolve);
53
- }
54
- });
55
- };
56
- }
57
- async function _applyPromised(fn, _this, args) {
58
- return await fn.apply(_this, args);
59
- }
60
-
61
- exports.debounce = debounce;
package/dist/index.d.ts DELETED
@@ -1,34 +0,0 @@
1
- interface DebounceOptions {
2
- /**
3
- Call the `fn` on the [leading edge of the timeout](https://css-tricks.com/debouncing-throttling-explained-examples/#article-header-id-1).
4
- Meaning immediately, instead of waiting for `wait` milliseconds.
5
- @default false
6
- */
7
- readonly leading?: boolean;
8
- /**
9
- Call the `fn` on trailing edge with last used arguments. Result of call is from previous call.
10
- @default false
11
- */
12
- readonly trailing?: boolean;
13
- }
14
- /**
15
- Debounce functions
16
- @param fn - Promise-returning/async function to debounce.
17
- @param wait - Milliseconds to wait before calling `fn`. Default value is 25ms
18
- @returns A function that delays calling `fn` until after `wait` milliseconds have elapsed since the last time it was called.
19
- @example
20
- ```
21
- import { debounce } from 'perfect-debounce';
22
- const expensiveCall = async input => input;
23
- const debouncedFn = debounce(expensiveCall, 200);
24
- for (const number of [1, 2, 3]) {
25
- console.log(await debouncedFn(number));
26
- }
27
- //=> 3
28
- //=> 3
29
- //=> 3
30
- ```
31
- */
32
- declare function debounce<ArgumentsT extends unknown[], ReturnT>(fn: (...args: ArgumentsT) => PromiseLike<ReturnT> | ReturnT, wait?: number, options?: DebounceOptions): (...args: ArgumentsT) => Promise<ReturnT>;
33
-
34
- export { DebounceOptions, debounce };