Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add before and after options, remove immediate option #9

Merged
merged 3 commits into from
Feb 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,27 @@ declare namespace debounceFn {
readonly wait?: number;

/**
Trigger the function on the leading edge instead of the trailing edge of the `wait` interval. For example, can be useful for preventing accidental double-clicks on a "submit" button from firing a second time.
Trigger the function on leading edge of `wait` interval. For example, can be useful for preventing accidental double-clicks on a "submit" button from firing a second time.

@default false
*/
readonly immediate?: boolean;
readonly before?: boolean;

/**
Trigger the function on trailing edge of `wait` interval.

@default true
*/
readonly after?: boolean;
}

interface BeforeOptions extends Options {
readonly before: true;
}

interface ImmediateOptions extends Options {
readonly immediate: true;
interface NoBeforeNoAfterOptions extends Options {
readonly after: false;
readonly before?: false;
}

interface DebouncedFunction<ArgumentsType extends unknown[], ReturnType> {
Expand Down Expand Up @@ -44,8 +56,14 @@ window.onresize = debounceFn(() => {
*/
declare function debounceFn<ArgumentsType extends unknown[], ReturnType>(
input: (...arguments: ArgumentsType) => ReturnType,
options: debounceFn.ImmediateOptions
options: debounceFn.BeforeOptions
): debounceFn.DebouncedFunction<ArgumentsType, ReturnType>;

declare function debounceFn<ArgumentsType extends unknown[], ReturnType>(
input: (...arguments: ArgumentsType) => ReturnType,
options: debounceFn.NoBeforeNoAfterOptions
): debounceFn.DebouncedFunction<ArgumentsType, undefined>;

declare function debounceFn<ArgumentsType extends unknown[], ReturnType>(
input: (...arguments: ArgumentsType) => ReturnType,
options?: debounceFn.Options
Expand Down
11 changes: 9 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ module.exports = (fn, options = {}) => {
throw new TypeError(`Expected the first argument to be a function, got \`${typeof fn}\``);
}

const before = (options.before === undefined) ? false : options.before;
const after = (options.after === undefined) ? true : options.after;
Comment on lines +9 to +10

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's worth using a regular Object.assign or {...} to define defaults and destructure all the options in one place.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big fan of memory allocations without any purpose, but since it's hard to expect this method to be used frequently, we can add allocation I think.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also use destructuring with defaults in the function parameters so no new objects would be created


if (!before && !after) {
throw new Error('Both `before` and `after` are false, function wouldn\'t be called.');
}

let timeout;
let result;

Expand All @@ -14,12 +21,12 @@ module.exports = (fn, options = {}) => {

const later = () => {
timeout = null;
if (!options.immediate) {
if (after) {
result = fn.apply(context, args);
}
};

const callNow = options.immediate && !timeout;
const callNow = before && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, options.wait || 0);

Expand Down
29 changes: 15 additions & 14 deletions index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import {expectType, expectError} from 'tsd';
import debounceFn = require('.');

const stringToBoolean = (string: string) => true;

const options: debounceFn.Options = {};
const debounced = debounceFn((string: string) => true);
expectType<debounceFn.DebouncedFunction<[string], boolean | undefined>>(
debounced
);
const debounced = debounceFn(stringToBoolean);
expectType<debounceFn.DebouncedFunction<[string], boolean | undefined>>(debounced);
expectType<boolean | undefined>(debounced('foo'));
debounced.cancel();

const debouncedWithoutOptions = debounceFn((string: string) => true);
expectType<boolean | undefined>(debouncedWithoutOptions('foo'));
expectError<boolean>(debouncedWithoutOptions('foo'));
expectType<boolean | undefined>(debounceFn(stringToBoolean)('foo'));
expectError<boolean>(debounceFn(stringToBoolean)('foo'));

expectType<boolean | undefined>(debounceFn(stringToBoolean, {wait: 20})('foo'));
expectError<boolean>(debounceFn(stringToBoolean, {wait: 20})('foo'));
expectType<boolean | undefined>(debounceFn(stringToBoolean, {after: true})('foo'));
expectError<boolean>(debounceFn(stringToBoolean, {after: true})('foo'));

const debouncedWithWait = debounceFn((string: string) => true, {wait: 100});
expectType<boolean | undefined>(debouncedWithWait('foo'));
expectError<boolean>(debouncedWithWait('foo'));
expectType<boolean>(debounceFn(stringToBoolean, {before: true})('foo'));
expectType<boolean>(debounceFn(stringToBoolean, {before: true, after: true})('foo'));

const debouncedWithImmediate = debounceFn((string: string) => true, {
immediate: true
});
expectType<boolean>(debouncedWithImmediate('foo'));
expectType<undefined>(debounceFn(stringToBoolean, {after: false})('foo'));
expectError<boolean>(debounceFn(stringToBoolean, {after: false})('foo'));
10 changes: 8 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,19 @@ Default: `0`

Time to wait until the `input` function is called.

##### immediate
##### before

Type: `boolean`<br>
Default: `false`

Trigger the function on the leading edge instead of the trailing edge of the `wait` interval. For example, can be useful for preventing accidental double-clicks on a "submit" button from firing a second time.
Trigger the function on leading edge of `wait` interval. For example, can be useful for preventing accidental double-clicks on a "submit" button from firing a second time.

##### after

Type: `boolean`<br>
Default: `true`

Trigger the function on trailing edge of `wait` interval.

## Related

Expand Down
69 changes: 67 additions & 2 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,30 @@ test('debounces a function', async t => {
t.is(count, 2);
});

test('immediate option', async t => {
test('before:false after:false options', t => {
t.throws(() => debounceFn(() => null, {
wait: 20,
before: false,
after: false
}), 'Both `before` and `after` are false, function wouldn\'t be called.');
});

test('before:true after:false options', async t => {
let count = 0;

const debounced = debounceFn(value => {
count++;
return value;
}, {
wait: 20,
immediate: true
before: true,
after: false
});

t.is(debounced(1), 1);
t.is(debounced(2), 1);
t.is(debounced(3), 1);
t.is(count, 1);

await delay(100);
t.is(debounced(4), 4);
Expand All @@ -51,6 +61,61 @@ test('immediate option', async t => {
t.is(count, 2);
});

test('before:false after:true options', async t => {
let count = 0;

const debounced = debounceFn(value => {
count++;
return value;
}, {
wait: 20,
before: false,
after: true
});

t.is(debounced(1), undefined);
t.is(debounced(2), undefined);
t.is(debounced(3), undefined);
t.is(count, 0);

await delay(100);
t.is(debounced(4), 3);
t.is(debounced(5), 3);
t.is(debounced(6), 3);
t.is(count, 1);

await delay(200);
t.is(count, 2);
});

test('before:true after:true options', async t => {
let count = 0;

const debounced = debounceFn(value => {
count++;
return value;
}, {
wait: 20,
before: true,
after: true
});

t.is(debounced(1), 1);
t.is(debounced(2), 1);
t.is(debounced(3), 1);
t.is(count, 1);

await delay(100);
t.is(count, 2);
t.is(debounced(4), 4);
t.is(debounced(5), 4);
t.is(debounced(6), 4);
t.is(count, 3);

await delay(200);
t.is(count, 4);
});

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

Expand Down