Skip to content

Commit

Permalink
feat: add Throttle function
Browse files Browse the repository at this point in the history
  • Loading branch information
duke-git committed Aug 9, 2024
1 parent 0bc5f82 commit 0f9764f
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 15 deletions.
43 changes: 43 additions & 0 deletions docs/api/packages/function.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
- [Xnor](#Xnor)
- [Nand](#Nand)
- [AcceptIf](#AcceptIf)
- [Throttle](#Throttle)


<div STYLE="page-break-after: always;"></div>
Expand Down Expand Up @@ -740,4 +741,46 @@ func main() {
// false
}

```

### <span id="Throttle">Throttle</span>

<p>创建一个函数的节流版本。返回的函数保证在每个时间间隔内最多只会被调用一次。</p>

<b>函数签名:</b>

```go
func Throttle(fn func(), interval time.Duration) func()
```

<b>示例:</b>

```go
package main

import (
"fmt"
"github.com/duke-git/lancet/v2/function"
)

func main() {
callCount := 0

fn := func() {
callCount++
}

throttledFn := function.Throttle(fn, 1*time.Second)

for i := 0; i < 5; i++ {
throttledFn()
}

time.Sleep(1 * time.Second)

fmt.Println(callCount)

// Output:
// 1
}
```
43 changes: 43 additions & 0 deletions docs/en/api/packages/function.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import (
- [Xnor](#Xnor)
- [Nand](#Nand)
- [AcceptIf](#AcceptIf)
- [Throttle](#Throttle)


<div STYLE="page-break-after: always;"></div>

Expand Down Expand Up @@ -738,5 +740,46 @@ func main() {
// 0
// false
}
```

### <span id="Throttle">Throttle</span>

<p>Throttle creates a throttled version of the provided function. The returned function guarantees that it will only be invoked at most once per interval.</p>

<b>Signature:</b>

```go
func Throttle(fn func(), interval time.Duration) func()
```

<b>Example:</b>

```go
package main

import (
"fmt"
"github.com/duke-git/lancet/v2/function"
)

func main() {
callCount := 0

fn := func() {
callCount++
}

throttledFn := function.Throttle(fn, 1*time.Second)

for i := 0; i < 5; i++ {
throttledFn()
}

time.Sleep(1 * time.Second)

fmt.Println(callCount)

// Output:
// 1
}
```
37 changes: 37 additions & 0 deletions function/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,43 @@ func Debounce(fn func(), delay time.Duration) (debouncedFn func(), cancelFn func
return debouncedFn, cancelFn
}

// Throttle creates a throttled version of the provided function.
// The returned function guarantees that it will only be invoked at most once per interval.
// Play: todo
func Throttle(fn func(), interval time.Duration) func() {
var (
timer *time.Timer
lastRun time.Time
mu sync.Mutex
)

return func() {
mu.Lock()
defer mu.Unlock()

now := time.Now()
if now.Sub(lastRun) >= interval {
fn()
lastRun = now
if timer != nil {
timer.Stop()
timer = nil
}
} else if timer == nil {
delay := interval - now.Sub(lastRun)

timer = time.AfterFunc(delay, func() {
mu.Lock()
defer mu.Unlock()

fn()
lastRun = time.Now()
timer = nil
})
}
}
}

// Schedule invoke function every duration time, util close the returned bool channel.
// Play: https://go.dev/play/p/hbON-Xeyn5N
func Schedule(d time.Duration, fn any, args ...any) chan bool {
Expand Down
21 changes: 21 additions & 0 deletions function/function_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,24 @@ func ExampleAcceptIf() {
// 0
// false
}

func ExampleThrottle() {
callCount := 0

fn := func() {
callCount++
}

throttledFn := Throttle(fn, 1*time.Second)

for i := 0; i < 5; i++ {
throttledFn()
}

time.Sleep(1 * time.Second)

fmt.Println(callCount)

// Output:
// 1
}
98 changes: 83 additions & 15 deletions function/function_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,11 @@ func TestDebounce(t *testing.T) {

t.Run("Single call", func(t *testing.T) {
callCount := 0
fn := func() {

debouncedFn, _ := Debounce(func() {
callCount++
}
}, 500*time.Millisecond)

debouncedFn, _ := Debounce(fn, 500*time.Millisecond)
debouncedFn()

time.Sleep(1 * time.Second)
Expand All @@ -144,11 +144,10 @@ func TestDebounce(t *testing.T) {

t.Run("Multiple calls within debounce interval", func(t *testing.T) {
callCount := 0
fn := func() {
callCount++
}

debouncedFn, _ := Debounce(fn, 1*time.Second)
debouncedFn, _ := Debounce(func() {
callCount++
}, 1*time.Second)

for i := 0; i < 5; i++ {
go func(index int) {
Expand All @@ -164,11 +163,10 @@ func TestDebounce(t *testing.T) {

t.Run("Immediate consecutive calls", func(t *testing.T) {
callCount := 0
fn := func() {
callCount++
}

debouncedFn, _ := Debounce(fn, 500*time.Millisecond)
debouncedFn, _ := Debounce(func() {
callCount++
}, 500*time.Millisecond)

for i := 0; i < 10; i++ {
debouncedFn()
Expand All @@ -182,11 +180,10 @@ func TestDebounce(t *testing.T) {

t.Run("Cancel calls", func(t *testing.T) {
callCount := 0
fn := func() {
callCount++
}

debouncedFn, cancelFn := Debounce(fn, 500*time.Millisecond)
debouncedFn, cancelFn := Debounce(func() {
callCount++
}, 500*time.Millisecond)

debouncedFn()

Expand All @@ -199,6 +196,77 @@ func TestDebounce(t *testing.T) {

}

func TestThrottle(t *testing.T) {
assert := internal.NewAssert(t, "TestThrottle")

t.Run("Single call", func(t *testing.T) {
callCount := 0

throttledFn := Throttle(func() {
callCount++
}, 1*time.Second)

throttledFn()

time.Sleep(100 * time.Millisecond)

assert.Equal(1, callCount)
})

t.Run("Multiple calls within throttle interval", func(t *testing.T) {
callCount := 0

throttledFn := Throttle(func() {
callCount++
}, 1*time.Second)

for i := 0; i < 5; i++ {
throttledFn()
}

time.Sleep(1 * time.Second)

assert.Equal(1, callCount)
})

t.Run("Mutiple calls space out throttle interval", func(t *testing.T) {
callCount := 0

throttledFn := Throttle(func() {
callCount++
}, 500*time.Millisecond)

throttledFn()
time.Sleep(600 * time.Millisecond)

throttledFn()
time.Sleep(600 * time.Millisecond)

throttledFn()

time.Sleep(1 * time.Second)

assert.Equal(3, callCount)
})

t.Run("Call function near the end of the interval", func(t *testing.T) {
callCount := 0

throttledFn := Throttle(func() {
callCount++
}, 1*time.Second)

throttledFn()
time.Sleep(900 * time.Millisecond)

throttledFn()
time.Sleep(200 * time.Millisecond)

assert.Equal(2, callCount)
})

}

func TestSchedule(t *testing.T) {
// assert := internal.NewAssert(t, "TestSchedule")

Expand Down

0 comments on commit 0f9764f

Please sign in to comment.