-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(debounce): initial implementation
- Loading branch information
Showing
9 changed files
with
1,278 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// Package debounce provides functions to debounce function calls, i.e., to | ||
// ensure that a function is only executed after a certain amount of time has | ||
// passed since the last call. | ||
// | ||
// Debouncing can be useful in scenarios where function calls may be triggered | ||
// rapidly, such as in response to user input, but the underlying operation is | ||
// expensive and only needs to be performed once per batch of calls. | ||
package debounce | ||
|
||
import ( | ||
"sync" | ||
"time" | ||
) | ||
|
||
// New returns a debounced function that delays invoking f until after wait time | ||
// has elapsed since the last time the debounced function was invoked. | ||
// | ||
// The returned cancel function can be used to cancel any pending invocation of | ||
// f, but is not required to be called, so can be ignored if not needed. | ||
// | ||
// Both debounced and cancel functions are safe for concurrent use in | ||
// goroutines, and can both be called multiple times. | ||
// | ||
// The debounced function does not wait for f to complete, so f needs to be | ||
// thread-safe as it may be invoked again before the previous invocation | ||
// completes. | ||
func New(wait time.Duration, f func()) (debounced func(), cancel func()) { | ||
var mux sync.Mutex | ||
timer := stoppedTimer(f) | ||
|
||
debounced = func() { | ||
mux.Lock() | ||
defer mux.Unlock() | ||
|
||
timer.Reset(wait) | ||
} | ||
|
||
cancel = func() { | ||
mux.Lock() | ||
defer mux.Unlock() | ||
|
||
timer.Stop() | ||
} | ||
|
||
return debounced, cancel | ||
} | ||
|
||
// NewWithMaxWait returns a debounced function like New, but with a maximum wait | ||
// time of maxWait, which is the maximum time f is allowed to be delayed before | ||
// it is invoked. | ||
// | ||
// The returned cancel function can be used to cancel any pending invocation of | ||
// f, but is not required to be called, so can be ignored if not needed. | ||
// | ||
// Both debounced and cancel functions are safe for concurrent use in | ||
// goroutines, and can both be called multiple times. | ||
// | ||
// The debounced function does not wait for f to complete, so f needs to be | ||
// thread-safe as it may be invoked again before the previous invocation | ||
// completes. | ||
func NewWithMaxWait( | ||
wait, maxWait time.Duration, | ||
f func(), | ||
) (debounced func(), cancel func()) { | ||
var mux sync.Mutex | ||
var dirty bool | ||
var timer *time.Timer | ||
var maxTimer *time.Timer | ||
|
||
cb := func() { | ||
mux.Lock() | ||
defer mux.Unlock() | ||
|
||
if !dirty { | ||
return | ||
} | ||
|
||
go f() | ||
timer.Stop() | ||
maxTimer.Stop() | ||
dirty = false | ||
} | ||
|
||
timer = stoppedTimer(cb) | ||
maxTimer = stoppedTimer(cb) | ||
|
||
debounced = func() { | ||
mux.Lock() | ||
defer mux.Unlock() | ||
|
||
timer.Reset(wait) | ||
|
||
// Mark as dirty, and start maxTimer if we were not already dirty. | ||
if !dirty { | ||
dirty = true | ||
maxTimer.Reset(maxWait) | ||
} | ||
} | ||
|
||
cancel = func() { | ||
mux.Lock() | ||
defer mux.Unlock() | ||
|
||
timer.Stop() | ||
maxTimer.Stop() | ||
dirty = false | ||
} | ||
|
||
return debounced, cancel | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package debounce_test | ||
|
||
import ( | ||
"fmt" | ||
"time" | ||
|
||
"github.com/romdo/go-debounce" | ||
) | ||
|
||
func ExampleNew() { | ||
// Create a new debouncer that will wait 100 milliseconds since the last | ||
// call before calling the callback function. | ||
debounced, _ := debounce.New(100*time.Millisecond, func() { | ||
fmt.Println("Hello, world!") | ||
}) | ||
|
||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 75ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 150ms | ||
debounced() | ||
time.Sleep(150 * time.Millisecond) // +150ms = 300ms, wait expired at 250ms | ||
|
||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 675ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 750ms | ||
debounced() | ||
time.Sleep(150 * time.Millisecond) // +150ms = 900ms, wait expired at 850ms | ||
|
||
// Output: | ||
// Hello, world! | ||
// Hello, world! | ||
} | ||
|
||
func ExampleNew_with_cancel() { | ||
// Create a new debouncer that will wait 100 milliseconds since the last | ||
// call before calling the callback function. | ||
debounced, cancel := debounce.New(100*time.Millisecond, func() { | ||
fmt.Println("Hello, world!") | ||
}) | ||
|
||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 75ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 150ms | ||
debounced() | ||
time.Sleep(150 * time.Millisecond) // +150ms = 300ms, wait expired at 250ms | ||
|
||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 375ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 450ms | ||
cancel() | ||
time.Sleep(150 * time.Millisecond) // +150ms = 600ms, canceled at 450ms | ||
|
||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 675ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 750ms | ||
debounced() | ||
time.Sleep(150 * time.Millisecond) // +150ms = 900ms, wait expired at 850ms | ||
|
||
// Output: | ||
// Hello, world! | ||
// Hello, world! | ||
} | ||
|
||
func ExampleNewWithMaxWait() { | ||
// Create a new debouncer that will wait 100 milliseconds since the last | ||
// call before calling the callback function. On repeated calls, it will | ||
// wait no more than 500 milliseconds before calling the callback function. | ||
debounced, _ := debounce.NewWithMaxWait( | ||
100*time.Millisecond, 500*time.Millisecond, | ||
func() { fmt.Println("Hello, world!") }, | ||
) | ||
|
||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 75ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 150ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 225ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 300ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 375ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 450ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 525ms, maxWait expired at 500ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 600ms | ||
debounced() | ||
time.Sleep(150 * time.Millisecond) // +150ms = 750ms, wait expired at 700ms | ||
|
||
// Output: | ||
// Hello, world! | ||
// Hello, world! | ||
} | ||
|
||
func ExampleNewWithMaxWait_with_cancel() { | ||
// Create a new debouncer that will wait 100 milliseconds since the last | ||
// call before calling the callback function. On repeated calls, it will | ||
// wait no more than 500 milliseconds before calling the callback function. | ||
debounced, cancel := debounce.NewWithMaxWait( | ||
100*time.Millisecond, 500*time.Millisecond, | ||
func() { fmt.Println("Hello, world!") }, | ||
) | ||
|
||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 75ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 150ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 225ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 300ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 375ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 450ms | ||
cancel() | ||
time.Sleep(150 * time.Millisecond) // +150ms = 600ms, canceled at 450ms | ||
debounced() | ||
time.Sleep(75 * time.Millisecond) // +75ms = 675ms | ||
debounced() | ||
time.Sleep(150 * time.Millisecond) // +150ms = 825ms, wait expired at 775ms | ||
|
||
// Output: | ||
// Hello, world! | ||
} |
Oops, something went wrong.