Skip to content

Commit 9b13deb

Browse files
committed
Fix count reset
1 parent e1d7195 commit 9b13deb

2 files changed

Lines changed: 64 additions & 35 deletions

File tree

v2/debounce.go

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,52 @@
1+
// Package debounce provides a generic debounce utility for channels.
2+
// It allows throttling the rate of emitted values using optional delay and/or limit mechanisms.
13
package debounce
24

35
import (
46
"time"
57
)
68

7-
// Option is a functional option for configuring the debouncer.
8-
type Option func(*debounceOptions)
9-
10-
type debounceOptions struct {
9+
// options encapsulates the debounce configuration: delay and limit.
10+
type options struct {
1111
limit int
1212
delay time.Duration
1313
}
1414

15-
// WithLimit sets the maximum number of incoming inputs before
16-
// passing most recent value downstream.
15+
// Option is a functional option for configuring the debouncer.
16+
type Option func(*options)
17+
18+
// WithLimit sets a maximum number of times the debounce delay can be reset
19+
// before a value is forcibly emitted. This acts as a safeguard against constant bouncing.
1720
func WithLimit(limit int) Option {
18-
return func(options *debounceOptions) {
21+
return func(options *options) {
1922
options.limit = limit
2023
}
2124
}
2225

23-
// WithDelay sets time.Duration specifying how long to wait after the last input
24-
// before sending the most recent value downstream.
26+
// WithDelay sets the debounce delay — the amount of quiet time (no new inputs)
27+
// required before emitting the most recent value.
2528
func WithDelay(d time.Duration) Option {
26-
return func(options *debounceOptions) {
29+
return func(options *options) {
2730
options.delay = d
2831
}
2932
}
3033

31-
// Chan wraps incoming channel and returns channel that emits the last value only
32-
// after no new values are received for the given delay or limit.
34+
// Chan wraps an input channel and returns a debounced output channel.
35+
// Debouncing behavior is defined by the combination of WithDelay and WithLimit:
36+
// - WithDelay delays value emission until no new values are received for `delay`.
37+
// - WithLimit limits the number of delay resets (i.e., bouncing) before emission is forced.
38+
//
39+
// If both are set, a value will be emitted after either the `delay` passes without new input,
40+
// or after the delay has been reset `limit` times.
3341
//
34-
// If no delay provided - zero delay assumed, so function returns in chan as result.
42+
// If delay is 0, the function returns the input channel unmodified.
3543
func Chan[T any](in <-chan T, opts ...Option) <-chan T {
36-
var options debounceOptions
44+
var options options
3745
for _, opt := range opts {
3846
opt(&options)
3947
}
4048

41-
// If there is no duration - every incoming element must be passed downstream.
49+
// Optimization: no debouncing if delay is zero
4250
if options.delay == 0 {
4351
return in
4452
}
@@ -48,16 +56,16 @@ func Chan[T any](in <-chan T, opts ...Option) <-chan T {
4856
defer close(out)
4957

5058
var (
51-
timer *time.Timer = time.NewTimer(options.delay)
52-
value T
53-
hasVal bool
54-
count int
59+
timer *time.Timer // Timer to manage delay
60+
lastValue T // Last received value
61+
hasValue bool // Whether a value is currently pending emission
62+
count int // Number of delay resets since last emission
5563
)
5664

5765
// Function to return the timer channel or nil if timer is not set
5866
// This avoids blocking on the timer channel if no timer is set
5967
timerOrNil := func() <-chan time.Time {
60-
if timer != nil && hasVal {
68+
if timer != nil {
6169
return timer.C
6270
}
6371
return nil
@@ -66,29 +74,44 @@ func Chan[T any](in <-chan T, opts ...Option) <-chan T {
6674
for {
6775
select {
6876
case v, ok := <-in:
69-
if !ok { // Input channel is closed, wrapping up
70-
if hasVal {
71-
out <- value
77+
if !ok {
78+
// Input channel closed — emit any pending value.
79+
if hasValue {
80+
out <- lastValue
7281
}
7382
return
7483
}
7584

76-
if options.limit != 0 { // If WithLimit specified as non-zero value start counting and emitting
77-
count++
78-
if count >= options.limit {
79-
out <- v
80-
hasVal = false
85+
lastValue = v
86+
hasValue = true
87+
88+
// On every new input, increment the reset count.
89+
count++
90+
91+
// Force emit if limit reached
92+
if options.limit != 0 && count >= options.limit {
93+
out <- v
94+
hasValue = false
95+
count = 0
96+
if timer != nil {
8197
timer.Stop()
82-
continue
8398
}
99+
continue
84100
}
85101

86-
value = v
87-
hasVal = true
88-
timer.Reset(options.delay)
102+
// Start or reset the delay timer
103+
if timer == nil {
104+
timer = time.NewTimer(options.delay)
105+
} else {
106+
timer.Reset(options.delay)
107+
}
89108
case <-timerOrNil():
90-
out <- value
91-
hasVal = false
109+
// Delay has passed — emit the last value
110+
if hasValue {
111+
out <- lastValue
112+
hasValue = false
113+
count = 0
114+
}
92115
}
93116
}
94117
}()

v2/debounce_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,17 @@ func TestDebounce_WithLimit(t *testing.T) {
8181
in <- 3
8282
time.Sleep(50 * time.Millisecond)
8383
in <- 4
84+
time.Sleep(50 * time.Millisecond)
85+
in <- 5
86+
time.Sleep(50 * time.Millisecond)
87+
in <- 6
88+
time.Sleep(50 * time.Millisecond)
89+
in <- 7
8490
time.Sleep(300 * time.Millisecond) // wait longer than debounce delay
8591
close(in)
8692
}()
8793

88-
expected := []int{3, 4}
94+
expected := []int{3, 6, 7}
8995
result := collect(out, 1*time.Second)
9096
if !slices.Equal(expected, result) {
9197
t.Errorf("expected result = %v, got %v", expected, result)

0 commit comments

Comments
 (0)