mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-12 16:52:29 +08:00
feat: add Throttle function
This commit is contained in:
@@ -41,6 +41,7 @@ import (
|
|||||||
- [Xnor](#Xnor)
|
- [Xnor](#Xnor)
|
||||||
- [Nand](#Nand)
|
- [Nand](#Nand)
|
||||||
- [AcceptIf](#AcceptIf)
|
- [AcceptIf](#AcceptIf)
|
||||||
|
- [Throttle](#Throttle)
|
||||||
|
|
||||||
|
|
||||||
<div STYLE="page-break-after: always;"></div>
|
<div STYLE="page-break-after: always;"></div>
|
||||||
@@ -741,3 +742,45 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### <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
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -41,6 +41,8 @@ import (
|
|||||||
- [Xnor](#Xnor)
|
- [Xnor](#Xnor)
|
||||||
- [Nand](#Nand)
|
- [Nand](#Nand)
|
||||||
- [AcceptIf](#AcceptIf)
|
- [AcceptIf](#AcceptIf)
|
||||||
|
- [Throttle](#Throttle)
|
||||||
|
|
||||||
|
|
||||||
<div STYLE="page-break-after: always;"></div>
|
<div STYLE="page-break-after: always;"></div>
|
||||||
|
|
||||||
@@ -738,5 +740,46 @@ func main() {
|
|||||||
// 0
|
// 0
|
||||||
// false
|
// 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
|
||||||
|
}
|
||||||
```
|
```
|
||||||
@@ -125,6 +125,43 @@ func Debounce(fn func(), delay time.Duration) (debouncedFn func(), cancelFn func
|
|||||||
return debouncedFn, cancelFn
|
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.
|
// Schedule invoke function every duration time, util close the returned bool channel.
|
||||||
// Play: https://go.dev/play/p/hbON-Xeyn5N
|
// Play: https://go.dev/play/p/hbON-Xeyn5N
|
||||||
func Schedule(d time.Duration, fn any, args ...any) chan bool {
|
func Schedule(d time.Duration, fn any, args ...any) chan bool {
|
||||||
|
|||||||
@@ -200,3 +200,24 @@ func ExampleAcceptIf() {
|
|||||||
// 0
|
// 0
|
||||||
// false
|
// 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -130,11 +130,11 @@ func TestDebounce(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Single call", func(t *testing.T) {
|
t.Run("Single call", func(t *testing.T) {
|
||||||
callCount := 0
|
callCount := 0
|
||||||
fn := func() {
|
|
||||||
callCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
debouncedFn, _ := Debounce(fn, 500*time.Millisecond)
|
debouncedFn, _ := Debounce(func() {
|
||||||
|
callCount++
|
||||||
|
}, 500*time.Millisecond)
|
||||||
|
|
||||||
debouncedFn()
|
debouncedFn()
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
@@ -144,11 +144,10 @@ func TestDebounce(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Multiple calls within debounce interval", func(t *testing.T) {
|
t.Run("Multiple calls within debounce interval", func(t *testing.T) {
|
||||||
callCount := 0
|
callCount := 0
|
||||||
fn := func() {
|
|
||||||
callCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
debouncedFn, _ := Debounce(fn, 1*time.Second)
|
debouncedFn, _ := Debounce(func() {
|
||||||
|
callCount++
|
||||||
|
}, 1*time.Second)
|
||||||
|
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
go func(index int) {
|
go func(index int) {
|
||||||
@@ -164,11 +163,10 @@ func TestDebounce(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Immediate consecutive calls", func(t *testing.T) {
|
t.Run("Immediate consecutive calls", func(t *testing.T) {
|
||||||
callCount := 0
|
callCount := 0
|
||||||
fn := func() {
|
|
||||||
callCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
debouncedFn, _ := Debounce(fn, 500*time.Millisecond)
|
debouncedFn, _ := Debounce(func() {
|
||||||
|
callCount++
|
||||||
|
}, 500*time.Millisecond)
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
debouncedFn()
|
debouncedFn()
|
||||||
@@ -182,11 +180,10 @@ func TestDebounce(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Cancel calls", func(t *testing.T) {
|
t.Run("Cancel calls", func(t *testing.T) {
|
||||||
callCount := 0
|
callCount := 0
|
||||||
fn := func() {
|
|
||||||
callCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
debouncedFn, cancelFn := Debounce(fn, 500*time.Millisecond)
|
debouncedFn, cancelFn := Debounce(func() {
|
||||||
|
callCount++
|
||||||
|
}, 500*time.Millisecond)
|
||||||
|
|
||||||
debouncedFn()
|
debouncedFn()
|
||||||
|
|
||||||
@@ -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) {
|
func TestSchedule(t *testing.T) {
|
||||||
// assert := internal.NewAssert(t, "TestSchedule")
|
// assert := internal.NewAssert(t, "TestSchedule")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user