From 0b29f0520dad65e5de17f6c42212d3c6783089c7 Mon Sep 17 00:00:00 2001 From: dudaodong Date: Thu, 8 Aug 2024 15:19:38 +0800 Subject: [PATCH] feat: add Debounce function --- docs/api/packages/function.md | 54 +++++++++++++++++++++- docs/en/api/packages/function.md | 51 ++++++++++++++++++++- function/function.go | 45 ++++++++++++++----- function/function_example_test.go | 26 +++++++++++ function/function_test.go | 74 +++++++++++++++++++++++++++++++ 5 files changed, 236 insertions(+), 14 deletions(-) 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")