diff --git a/docs/api/packages/function.md b/docs/api/packages/function.md index 770116d..94bc899 100644 --- a/docs/api/packages/function.md +++ b/docs/api/packages/function.md @@ -28,7 +28,8 @@ import ( - [Before](#Before) - [CurryFn](#CurryFn) - [Compose](#Compose) -- [Debounced](#Debounced) +- [Debounce](#Debounce) +- [Debounceddeprecated](#Debounced) - [Delay](#Delay) - [Schedule](#Schedule) - [Pipeline](#Pipeline) @@ -193,9 +194,58 @@ func main() { } ``` +### Debounce + +

创建一个函数的去抖动版本。该去抖动函数仅在上次调用后的指定延迟时间过去之后才会调用原始函数。支持取消去抖动。

+ +函数签名: + +```go +func Debounce(fn func(), delay time.Duration) (debouncedFn func(), cancelFn func()) +``` + +示例: + +```go +package main + +import ( + "fmt" + "github.com/duke-git/lancet/v2/function" +) + +func main() { + callCount := 0 + fn := func() { + callCount++ + } + + debouncedFn, _ := function.Debounce(fn, 500*time.Millisecond) + + for i := 0; i < 10; i++ { + debouncedFn() + time.Sleep(50 * time.Millisecond) + } + + time.Sleep(1 * time.Second) + fmt.Println(callCount) + + debouncedFn() + + time.Sleep(1 * time.Second) + fmt.Println(callCount) + + // Output: + // 1 + // 2 +} +``` + ### Debounced -

创建一个debounced函数,该函数延迟调用fn直到自上次调用debounced函数后等待持续时间过去。

+

创建一个函数的去抖动版本。

+ +> ⚠️ 本函数已弃用. 使用 `Debounce` 代替. 函数签名: diff --git a/docs/en/api/packages/function.md b/docs/en/api/packages/function.md index 4c31cf1..1d0bb56 100644 --- a/docs/en/api/packages/function.md +++ b/docs/en/api/packages/function.md @@ -28,7 +28,8 @@ import ( - [Before](#Before) - [CurryFn](#CurryFn) - [Compose](#Compose) -- [Debounced](#Debounced) +- [Debounce](#Debounce) +- [Debounceddeprecated](#Debounced) - [Delay](#Delay) - [Schedule](#Schedule) - [Pipeline](#Pipeline) @@ -191,11 +192,59 @@ func main() { // ABCDE } ``` +### Debounce + +

Creates a debounced version of the provided function. The debounced function will only invoke the original function after the specified delay has passed since the last time it was invoked. It also supports canceling the debounce.

+ +Signature: + +```go +func Debounce(fn func(), delay time.Duration) (debouncedFn func(), cancelFn func()) +``` + +Example: + +```go +package main + +import ( + "fmt" + "github.com/duke-git/lancet/v2/function" +) + +func main() { + callCount := 0 + fn := func() { + callCount++ + } + + debouncedFn, _ := function.Debounce(fn, 500*time.Millisecond) + + for i := 0; i < 10; i++ { + debouncedFn() + time.Sleep(50 * time.Millisecond) + } + + time.Sleep(1 * time.Second) + fmt.Println(callCount) + + debouncedFn() + + time.Sleep(1 * time.Second) + fmt.Println(callCount) + + // Output: + // 1 + // 2 +} +``` ### Debounced

Creates a debounced function that delays invoking fn until after wait duration have elapsed since the last time the debounced function was invoked.

+> ⚠️ This function is deprecated. use `Debounce` instead. + Signature: ```go diff --git a/function/function.go b/function/function.go index 3100467..96d6cc0 100644 --- a/function/function.go +++ b/function/function.go @@ -7,6 +7,7 @@ package function import ( "fmt" "reflect" + "sync" "time" ) @@ -84,22 +85,44 @@ func Delay(delay time.Duration, fn any, args ...any) { } // Debounced creates a debounced function that delays invoking fn until after wait duration have elapsed since the last time the debounced function was invoked. +// Deprecated: Use Debounce function instead. // Play: https://go.dev/play/p/absuEGB_GN7 -func Debounced(fn func(), duration time.Duration) func() { - // Catch programming error while constructing the closure - mustBeFunction(fn) +func Debounced(fn func(), delay time.Duration) func() { + debouncedFn, _ := Debounce(fn, delay) + return debouncedFn +} - timer := time.NewTimer(duration) - timer.Stop() +// Debounce creates a debounced version of the provided function. +// Play: todo +func Debounce(fn func(), delay time.Duration) (debouncedFn func(), cancelFn func()) { + var ( + timer *time.Timer + mu sync.Mutex + ) - go func() { - for { - <-timer.C - go fn() + debouncedFn = func() { + mu.Lock() + defer mu.Unlock() + + if timer != nil { + timer.Stop() } - }() - return func() { timer.Reset(duration) } + timer = time.AfterFunc(delay, func() { + fn() + }) + } + + cancelFn = func() { + mu.Lock() + defer mu.Unlock() + + if timer != nil { + timer.Stop() + } + } + + return debouncedFn, cancelFn } // Schedule invoke function every duration time, util close the returned bool channel. diff --git a/function/function_example_test.go b/function/function_example_test.go index f4d9562..245d02a 100644 --- a/function/function_example_test.go +++ b/function/function_example_test.go @@ -79,6 +79,32 @@ func ExampleDelay() { // hello } +func ExampleDebounce() { + callCount := 0 + fn := func() { + callCount++ + } + + debouncedFn, _ := Debounce(fn, 500*time.Millisecond) + + for i := 0; i < 10; i++ { + debouncedFn() + time.Sleep(50 * time.Millisecond) + } + + time.Sleep(1 * time.Second) + fmt.Println(callCount) + + debouncedFn() + + time.Sleep(1 * time.Second) + fmt.Println(callCount) + + // Output: + // 1 + // 2 +} + func ExampleDebounced() { count := 0 add := func() { diff --git a/function/function_test.go b/function/function_test.go index 18fe547..99d7d6f 100644 --- a/function/function_test.go +++ b/function/function_test.go @@ -125,6 +125,80 @@ func TestDebounced(t *testing.T) { assert.Equal(2, count) } +func TestDebounce(t *testing.T) { + assert := internal.NewAssert(t, "TestDebounce") + + t.Run("Single call", func(t *testing.T) { + callCount := 0 + fn := func() { + callCount++ + } + + debouncedFn, _ := Debounce(fn, 500*time.Millisecond) + debouncedFn() + + time.Sleep(1 * time.Second) + + assert.Equal(1, callCount) + }) + + t.Run("Multiple calls within debounce interval", func(t *testing.T) { + callCount := 0 + fn := func() { + callCount++ + } + + debouncedFn, _ := Debounce(fn, 1*time.Second) + + for i := 0; i < 5; i++ { + go func(index int) { + time.Sleep(time.Duration(index) * 200 * time.Millisecond) + debouncedFn() + }(i) + } + + time.Sleep(2 * time.Second) + + assert.Equal(1, callCount) + }) + + t.Run("Immediate consecutive calls", func(t *testing.T) { + callCount := 0 + fn := func() { + callCount++ + } + + debouncedFn, _ := Debounce(fn, 500*time.Millisecond) + + for i := 0; i < 10; i++ { + debouncedFn() + time.Sleep(50 * time.Millisecond) + } + + time.Sleep(1 * time.Second) + + assert.Equal(1, callCount) + }) + + t.Run("Cancel calls", func(t *testing.T) { + callCount := 0 + fn := func() { + callCount++ + } + + debouncedFn, cancelFn := Debounce(fn, 500*time.Millisecond) + + debouncedFn() + + cancelFn() + + time.Sleep(1 * time.Second) + + assert.Equal(0, callCount) + }) + +} + func TestSchedule(t *testing.T) { // assert := internal.NewAssert(t, "TestSchedule")