Skip to content

Commit

Permalink
Add before and after options, remove immediate option (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
Yanis Benson authored Feb 19, 2020
1 parent 361f385 commit 689f847
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 25 deletions.
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;

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

0 comments on commit 689f847

Please sign in to comment.