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.
13package debounce
24
35import (
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.
1720func 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.
2528func 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 .
3543func 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 }()
0 commit comments