Skip to content

Commit 258e4da

Browse files
committed
✨ [util] 新增func_debounce,新增timmers
1 parent ef6488b commit 258e4da

File tree

7 files changed

+283
-56
lines changed

7 files changed

+283
-56
lines changed

deno.lock

Lines changed: 17 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

util/deno.json

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,32 @@
11
{
2-
"name": "@gaubee/util",
3-
"version": "0.21.1",
4-
"exports": {
5-
"./abort": "./src/abort.ts",
6-
"./bigint": "./src/bigint.ts",
7-
"./collections": "./src/collections.ts",
8-
"./date": "./src/date.ts",
9-
"./decorators-legacy": "./src/decorators-legacy.ts",
10-
"./decorators": "./src/decorators.ts",
11-
"./disposable": "./src/disposable.ts",
12-
"./encoding": "./src/encoding.ts",
13-
"./event_target": "./src/event_target.ts",
14-
"./func": "./src/func.ts",
15-
"./generator": "./src/generator.ts",
16-
"./lrc": "./src/lrc.ts",
17-
"./map": "./src/map.ts",
18-
"./math": "./src/math.ts",
19-
"./number": "./src/number.ts",
20-
"./object": "./src/object.ts",
21-
"./promise": "./src/promise.ts",
22-
"./pure_event": "./src/pure_event.ts",
23-
"./readable_stream": "./src/readable_stream.ts",
24-
"./string": "./src/string.ts",
25-
".": "./src/index.ts"
26-
},
27-
"publish": {
28-
"include": [
29-
"LICENSE",
30-
"README.md",
31-
"src/**/*.ts"
32-
],
33-
"exclude": [
34-
"src/**/*.bench.ts",
35-
"src/**/*.test.ts"
36-
]
37-
}
38-
}
2+
"name": "@gaubee/util",
3+
"version": "0.22.0",
4+
"exports": {
5+
"./abort": "./src/abort.ts",
6+
"./bigint": "./src/bigint.ts",
7+
"./collections": "./src/collections.ts",
8+
"./date": "./src/date.ts",
9+
"./debounce": "./src/debounce.ts",
10+
"./decorators-legacy": "./src/decorators-legacy.ts",
11+
"./decorators": "./src/decorators.ts",
12+
"./disposable": "./src/disposable.ts",
13+
"./encoding": "./src/encoding.ts",
14+
"./event_target": "./src/event_target.ts",
15+
"./func": "./src/func.ts",
16+
"./generator": "./src/generator.ts",
17+
"./lrc": "./src/lrc.ts",
18+
"./map": "./src/map.ts",
19+
"./math": "./src/math.ts",
20+
"./number": "./src/number.ts",
21+
"./object": "./src/object.ts",
22+
"./promise": "./src/promise.ts",
23+
"./pure_event": "./src/pure_event.ts",
24+
"./readable_stream": "./src/readable_stream.ts",
25+
"./string": "./src/string.ts",
26+
".": "./src/index.ts"
27+
},
28+
"publish": {
29+
"include": ["LICENSE", "README.md", "src/**/*.ts"],
30+
"exclude": ["src/**/*.bench.ts", "src/**/*.test.ts"]
31+
}
32+
}

util/src/debounce.test.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { func_debounce } from "./debounce.ts";
2+
import assert from "node:assert";
3+
import { delay } from "./promise.ts";
4+
5+
Deno.test("should debounce function calls", async () => {
6+
let callCount = 0;
7+
const fn = () => {
8+
callCount++;
9+
};
10+
11+
const debounced = func_debounce(fn, 100);
12+
13+
debounced();
14+
debounced();
15+
debounced();
16+
17+
assert.equal(callCount, 0);
18+
19+
await delay(200);
20+
21+
assert.equal(callCount, 1);
22+
});
23+
24+
Deno.test("should call the function immediately if `before` option is true", async () => {
25+
let callCount = 0;
26+
const fn = () => {
27+
callCount++;
28+
};
29+
30+
const debounced = func_debounce(fn, 100, { before: true });
31+
32+
debounced();
33+
34+
assert.equal(callCount, 1);
35+
36+
await delay(200);
37+
38+
assert.equal(callCount, 1);
39+
});
40+
41+
Deno.test("should clear the timer with `cancel` method", async () => {
42+
let callCount = 0;
43+
const fn = () => {
44+
callCount++;
45+
};
46+
47+
const debounced = func_debounce(fn, 100);
48+
49+
debounced();
50+
debounced.clear();
51+
52+
await delay(200);
53+
54+
assert.equal(callCount, 0);
55+
});
56+
57+
Deno.test("should flush the timer with `flush` method", async () => {
58+
let callCount = 0;
59+
const fn = () => {
60+
callCount++;
61+
};
62+
63+
const debounced = func_debounce(fn, 100);
64+
65+
debounced();
66+
debounced.flush();
67+
68+
assert.equal(callCount, 1);
69+
70+
await delay(200);
71+
72+
assert.equal(callCount, 1);
73+
});
74+
75+
Deno.test("should return a promise that resolves with the function result", async () => {
76+
const fn = () => "result";
77+
78+
const debounced = func_debounce(fn, 100);
79+
80+
const result = await debounced();
81+
82+
assert.equal(result, "result");
83+
});
84+
85+
Deno.test("should have `isPending` property", async () => {
86+
let callCount = 0;
87+
const fn = () => {
88+
callCount++;
89+
};
90+
91+
const debounced = func_debounce(fn, 100);
92+
93+
assert(!debounced.isPending);
94+
95+
debounced();
96+
97+
assert(debounced.isPending);
98+
99+
await delay(200);
100+
101+
assert(!debounced.isPending);
102+
});

util/src/debounce.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import type { Func } from "./func.ts";
2+
import { obj_assign_props } from "./object.ts";
3+
import { type Timmer, timmers } from "./promise.ts";
4+
5+
export namespace func_debounce {
6+
export type DebouncedFunction<F extends Func> = Func.SetReturn<F, Promise<Func.Return<F>>> & {
7+
readonly isPending: boolean;
8+
clear(): void;
9+
source: F;
10+
flush(): void;
11+
};
12+
}
13+
14+
export const func_debounce = <T extends Func>(
15+
fn: T,
16+
wait: number | Timmer = 0,
17+
options: {
18+
/**
19+
* 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.
20+
*/
21+
before?: boolean;
22+
} = {},
23+
): func_debounce.DebouncedFunction<T> => {
24+
if (!Number.isFinite(wait)) {
25+
throw new TypeError("Expected `wait` to be a finite number");
26+
}
27+
const timmer = timmers.from(wait);
28+
29+
let leadingValue: R;
30+
let clear: Timmer.Clear | undefined;
31+
type R = Func.Return<T>;
32+
let jobList: PromiseWithResolvers<R>[] = [];
33+
34+
let trigger: Func | undefined;
35+
return obj_assign_props(function (this: Func.This<T>, ...args: Func.Args<T>) {
36+
const job = Promise.withResolvers<R>();
37+
const shouldCallNow = options.before && null == clear;
38+
39+
if (shouldCallNow) {
40+
leadingValue = fn.apply(this, args);
41+
job.resolve(leadingValue);
42+
} else {
43+
jobList.push(job);
44+
}
45+
46+
clear?.();
47+
clear = timmer(
48+
trigger = () => {
49+
clear = undefined;
50+
trigger = undefined;
51+
52+
const result = options.before ? leadingValue : fn.apply(this, args);
53+
54+
for (const job of jobList) {
55+
job.resolve(result);
56+
}
57+
58+
jobList = [];
59+
},
60+
);
61+
62+
return job.promise;
63+
}, {
64+
get isPending() {
65+
return clear != null;
66+
},
67+
clear() {
68+
clear?.();
69+
},
70+
source: fn,
71+
flush() {
72+
if (clear != null) {
73+
clear();
74+
trigger?.();
75+
}
76+
},
77+
});
78+
};

util/src/func.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,23 @@ export type Func<This = any, Arguments extends readonly unknown[] = any[], Retur
3131
this: This,
3232
...args: Arguments
3333
) => Return;
34-
type FunReturn<F> = F extends Func ? ReturnType<F> : undefined;
34+
export namespace Func {
35+
export type Return<F> = F extends Func ? ReturnType<F> : never;
36+
export type Args<F> = F extends Func ? Parameters<F> : never;
37+
export type This<F> = F extends Func<infer T> ? T : never;
38+
export type SetReturn<T, R> = Func<This<T>, Args<T>, R>;
39+
}
3540
type KeyFun<F extends Func> = Func<ThisParameterType<F>, Parameters<F>>;
3641
export type FuncRemember<
3742
F extends Func,
3843
K extends (KeyFun<F> | void) = void,
3944
> = F & {
4045
readonly source: F;
41-
readonly key: FunReturn<K> | undefined;
46+
readonly key: Func.Return<K> | undefined;
4247
readonly runned: boolean;
43-
readonly returnValue: ReturnType<F> | undefined;
48+
readonly returnValue: Func.Return<F> | undefined;
4449
reset(): void;
45-
rerun(...args: Parameters<F>): ReturnType<F>;
50+
rerun(...args: Parameters<F>): Func.Return<F>;
4651
};
4752
/**
4853
* 让一个函数的返回结果是缓存的
@@ -54,8 +59,8 @@ export const func_remember = <
5459
K extends Func<ThisParameterType<F>, Parameters<F>> | void | void,
5560
>(func: F, key?: K): FuncRemember<F, K> => {
5661
let result: {
57-
key: FunReturn<K>;
58-
res: ReturnType<F>;
62+
key: Func.Return<K>;
63+
res: Func.Return<F>;
5964
} | undefined;
6065

6166
const once_fn = function (
@@ -91,7 +96,7 @@ export const func_remember = <
9196
},
9297
rerun(...args: Parameters<F>) {
9398
once_fn_mix.reset();
94-
return once_fn_mix(...args) as ReturnType<F>;
99+
return once_fn_mix(...args) as Func.Return<F>;
95100
},
96101
});
97102
Object.defineProperties(once_fn_mix, {

util/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from "./generator.ts";
33
export * from "./bigint.ts";
44
export * from "./collections.ts";
55
export * from "./date.ts";
6+
export * from "./debounce.ts";
67
export * from "./decorators-legacy.ts";
78
export * from "./decorators.ts";
89
export * from "./disposable.ts";

0 commit comments

Comments
 (0)