Skip to content

Commit

Permalink
Require Node.js 12 and add .promise() (#13)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
Richienb and sindresorhus authored Jan 18, 2021
1 parent b59d1cf commit 43e62cf
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 36 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ jobs:
node-version:
- 14
- 12
- 10
- 8
- 6
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
Expand Down
40 changes: 30 additions & 10 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ declare const pDebounce: {
@example
```
import pDebounce = require('p-debounce');
import pDebounce from 'p-debounce';
const expensiveCall = async input => input;
Expand All @@ -39,14 +39,34 @@ declare const pDebounce: {
options?: pDebounce.Options
): (...arguments: ArgumentsType) => Promise<ReturnType>;

// TODO: Remove this for the next major release, refactor the whole definition to:
// declare function pDebounce<ArgumentsType extends unknown[], ReturnType>(
// fn: (...arguments: ArgumentsType) => PromiseLike<ReturnType> | ReturnType,
// wait: number,
// options?: pDebounce.Options
// ): (...arguments: ArgumentsType) => Promise<ReturnType>;
// export = pDebounce;
default: typeof pDebounce;
/**
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.
@param function_ - Promise-returning/async function to debounce.
@example
```
import pDebounce from 'p-debounce';
import {setTimeout as delay} = from 'timers/promises';
const expensiveCall = async value => {
await delay(200);
return value;
}
const debouncedFn = pDebounce.promise(expensiveCall);
for (const number of [1, 2, 3]) {
console.log(await debouncedFn(number));
}
//=> 3
//=> 3
//=> 3
```
*/
promise<ArgumentsType extends unknown[], ReturnType>(
function_: (...arguments: ArgumentsType) => PromiseLike<ReturnType> | ReturnType
): (...arguments: ArgumentsType) => Promise<ReturnType>;
};

export = pDebounce;
export default pDebounce;
21 changes: 18 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ const pDebounce = (fn, wait, options = {}) => {
};
};

module.exports = pDebounce;
// TODO: Remove this for the next major release
module.exports.default = pDebounce;
pDebounce.promise = function_ => {
let currentPromise;

return async (...arguments_) => {
if (currentPromise) {
return currentPromise;
}

try {
currentPromise = function_(...arguments_);
return await currentPromise;
} finally {
currentPromise = undefined;
}
};
};

export default pDebounce;
5 changes: 4 additions & 1 deletion index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import {expectType} from 'tsd';
import pDebounce = require('.');
import pDebounce from './index.js';

const expensiveCall = async (input: number) => input;

expectType<(input: number) => Promise<number>>(pDebounce(expensiveCall, 200));
expectType<(input: number) => Promise<number>>(
pDebounce(expensiveCall, 200, {leading: true})
);
expectType<(input: number) => Promise<number>>(
pDebounce.promise(expensiveCall)
);
16 changes: 9 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"type": "module",
"exports": "./index.js",
"engines": {
"node": ">=6"
"node": ">=12"
},
"scripts": {
"test": "xo && ava && tsd"
Expand Down Expand Up @@ -41,11 +43,11 @@
"bluebird"
],
"devDependencies": {
"ava": "^1.4.1",
"delay": "^4.1.0",
"in-range": "^1.0.0",
"time-span": "^3.0.0",
"tsd": "^0.7.2",
"xo": "^0.24.0"
"ava": "^3.15.0",
"in-range": "^2.0.0",
"time-span": "^4.0.0",
"tsd": "^0.14.0",
"xo": "^0.37.1",
"yoctodelay": "^1.1.0"
}
}
39 changes: 32 additions & 7 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,32 @@

> [Debounce](https://css-tricks.com/debouncing-throttling-explained-examples/) promise-returning & async functions

## Install

```
$ npm install p-debounce
```


## Usage

```js
const pDebounce = require('p-debounce');
import pDebounce from 'p-debounce';

const expensiveCall = async input => input;

const debouncedFn = pDebounce(expensiveCall, 200);

for (const i of [1, 2, 3]) {
debouncedFn(i).then(console.log);
for (const number of [1, 2, 3]) {
console.log(await debouncedFn(number));
}
//=> 3
//=> 3
//=> 3
```


## API

### pDebounce(fn, wait, [options])
### pDebounce(fn, wait, options?)

Returns a function that delays calling `fn` until after `wait` milliseconds have elapsed since the last time it was called.

Expand All @@ -57,6 +54,34 @@ Default: `false`

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.

### pDebounce.promise(function_)

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.

#### function_

Type: `Function`

Promise-returning/async function to debounce.

```js
import pDebounce from 'p-debounce';
import {setTimeout as delay} = from 'timers/promises';

const expensiveCall = async value => {
await delay(200);
return value;
}

const debouncedFn = pDebounce.promise(expensiveCall);

for (const number of [1, 2, 3]) {
console.log(await debouncedFn(number));
}
//=> 3
//=> 3
//=> 3
```

## Related

Expand Down
27 changes: 22 additions & 5 deletions test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import test from 'ava';
import delay from 'delay';
import delay from 'yoctodelay'; // TODO: Replace with `import {setTimeout as delay} = from 'timers/promises';` when targeting Node.js 16
import inRange from 'in-range';
import timeSpan from 'time-span';
import pDebounce from '.';
import pDebounce from './index.js';

const fixture = Symbol('fixture');

Expand All @@ -21,16 +21,33 @@ test('multiple calls', async t => {
return value;
}, 100);

const results = await Promise.all([1, 2, 3, 4, 5].map(debounced));
const results = await Promise.all([1, 2, 3, 4, 5].map(value => debounced(value)));

t.deepEqual(results, [5, 5, 5, 5, 5]);
t.is(count, 1);
t.true(inRange(end(), 130, 170));
t.true(inRange(end(), {
start: 130,
end: 170
}));

await delay(200);
t.is(await debounced(6), 6);
});

test('.promise()', async t => {
let count = 0;

const debounced = pDebounce.promise(async () => {
await delay(50);
count++;
return count;
});

t.deepEqual(await Promise.all([1, 2, 3, 4, 5].map(value => debounced(value))), [1, 1, 1, 1, 1]);

t.is(await debounced(), 2);
});

test('leading option', async t => {
let count = 0;

Expand All @@ -40,7 +57,7 @@ test('leading option', async t => {
return value;
}, 100, {leading: true});

const results = await Promise.all([1, 2, 3, 4].map(debounced));
const results = await Promise.all([1, 2, 3, 4].map(value => debounced(value)));

t.deepEqual(results, [1, 1, 1, 1], 'value from the first promise is used without the timeout');
t.is(count, 1);
Expand Down

0 comments on commit 43e62cf

Please sign in to comment.