-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdebouncer.go
101 lines (85 loc) · 2.53 KB
/
debouncer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package debouncer
import (
"context"
"sync"
"time"
)
// Action method signature of the action
// which has to be performed on event
type Action func(ctx context.Context, value interface{}) error
type Debouncer struct {
// Input represents the change event
Input chan interface{}
// Interval represent the max time it should wait
// before performing the Action
Interval time.Duration
// once will be used to ensure that the Do method
// is called only once per deboubncer instance
// as a single debounce can take care of only one operation
// calling it multiple times might cause inconsistencies
once sync.Once
}
// NewDebouncer creates a new instance of debouncer
// this will create an unbuffered channel to capture a event
func NewDebouncer(interval time.Duration) *Debouncer {
return &Debouncer{
Input: make(chan interface{}),
Interval: interval,
}
}
// TriggerAction records an event to perform the Action provide
// this will add given value to the input channel as notification
// for debouncer
func (d *Debouncer) TriggerAction(val interface{}) {
d.Input <- val
}
// Do will run the debounce in a go routine
// and it'll make sure that its been invoked only once
// as multiple action can not fall under same config
func (d *Debouncer) Do(ctx context.Context, action Action) {
// ensure debouncing is started only once per instance
d.once.Do(func() {
go d.debounce(ctx, action)
})
}
// debounce will make sure that the given action is not performed repeatedly
// if its triggered multiple times within a given interval
func (d *Debouncer) debounce(ctx context.Context, action Action) {
var (
// hasEvent represents of there is a valid event received
// this will help avoid unnecessary triggering if the method
hasEvent bool
// value holds the latest input received
value interface{}
)
// trigger the action post the interval duration specified
timer := time.AfterFunc(d.Interval, func() {
if hasEvent {
_ = action(ctx, value)
hasEvent = false
}
})
for {
select {
// if there is an event then reset the timer
// and update the hasEvent to true representing
// to trigger the function once the timer ends
case value = <-d.Input:
hasEvent = true
// stop the previous timer
timer.Stop()
// drain the channel if there any event
// else just continue
select {
case <-timer.C:
default:
}
// reset the timer post draining the channel
timer.Reset(d.Interval)
// if the application is being terminated
// then stop the debouncing
case <-ctx.Done():
return
}
}
}