-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathdebouncer.go
177 lines (149 loc) · 4.52 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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package godebouncer
import (
"sync"
"time"
)
type Debouncer struct {
timeDuration time.Duration
timer *time.Timer
triggeredFunc func()
signalCalledAt time.Time
signalCalledInDuration int
mu sync.Mutex
done chan struct{}
options Options
}
type Options struct {
Leading, Trailing bool
}
type DebouncerOptions func(*Debouncer)
type DebouncerType string
const (
TRAILING DebouncerType = "trailing"
LEADING DebouncerType = "leading"
OVERLAPPED DebouncerType = "overlapped"
INACTIVE DebouncerType = "inactive"
)
// New creates a new instance of debouncer. Each instance of debouncer works independent, concurrency with different wait duration.
func New(duration time.Duration) *Debouncer {
return &Debouncer{timeDuration: duration, triggeredFunc: func() {}, options: Options{false, true}}
}
// NewWithOptions takes a slice of option as the rest arguments and return a new instance of debouncer
func NewWithOptions(opts ...DebouncerOptions) *Debouncer {
var (
defaultDuration = 1 * time.Minute
defaultOptions = Options{false, true}
defaultTriggeredFunc = func() {}
)
d := &Debouncer{
timeDuration: defaultDuration,
triggeredFunc: defaultTriggeredFunc,
options: defaultOptions,
done: make(chan struct{}),
}
for _, opt := range opts {
opt(d)
}
return d
}
// WithOptions sets the options of debouncer instance.
func WithOptions(options Options) DebouncerOptions {
return func(d *Debouncer) {
d.options = options
}
}
// WithTriggered sets the triggered function of debouncer instance.
func WithTriggered(triggeredFunc func()) DebouncerOptions {
return func(d *Debouncer) {
d.triggeredFunc = triggeredFunc
}
}
// WithTimeDuration sets the time duration of debouncer instance.
func WithTimeDuration(timeDuration time.Duration) DebouncerOptions {
return func(d *Debouncer) {
d.timeDuration = timeDuration
}
}
// WithTriggered attached a triggered function to debouncer instance and return the same instance of debouncer to use.
func (d *Debouncer) WithTriggered(triggeredFunc func()) *Debouncer {
d.triggeredFunc = triggeredFunc
return d
}
// SendSignal makes an action that notifies to invoke the triggered function after a wait duration.
func (d *Debouncer) SendSignal() {
d.mu.Lock()
defer d.mu.Unlock()
now := time.Now()
switch d.getDebounceType() {
case TRAILING:
d.Cancel()
d.timer = d.invokeTriggeredFunc()
case LEADING:
if d.signalCalledAt.IsZero() || now.Sub(d.signalCalledAt) > d.timeDuration {
d.timer = d.invokeTriggeredFunc()
}
case OVERLAPPED:
if d.signalCalledAt.IsZero() || now.Sub(d.signalCalledAt) >= d.timeDuration {
d.timer = d.invokeTriggeredFunc()
break
}
if d.signalCalledInDuration > 0 {
if d.signalCalledInDuration > 1 {
d.Cancel()
}
d.timer = d.invokeTriggeredFunc()
}
d.signalCalledInDuration += 1
default:
}
}
func (d *Debouncer) invokeTriggeredFunc() *time.Timer {
d.signalCalledAt = time.Now()
return time.AfterFunc(d.timeDuration, func() {
d.triggeredFunc()
if d.done != nil {
close(d.done)
}
d.done = make(chan struct{})
d.signalCalledInDuration = 0
})
}
// getDebounceType get the debouncer time based on Debouncer's options
func (d *Debouncer) getDebounceType() DebouncerType {
if !d.options.Leading && d.options.Trailing {
return TRAILING
}
if d.options.Leading && !d.options.Trailing {
return LEADING
}
if d.options.Leading && d.options.Trailing {
return OVERLAPPED
}
return INACTIVE
}
// Do run the signalFunc() and call SendSignal() after all. The signalFunc() and SendSignal() function run sequentially.
func (d *Debouncer) Do(signalFunc func()) {
signalFunc()
d.SendSignal()
}
// Cancel the timer from the last function SendSignal(). The scheduled triggered function is cancelled and doesn't invoke.
func (d *Debouncer) Cancel() {
if d.timer != nil {
d.timer.Stop()
}
}
// UpdateTriggeredFunc replaces triggered function.
func (d *Debouncer) UpdateTriggeredFunc(newTriggeredFunc func()) {
d.triggeredFunc = newTriggeredFunc
}
// UpdateTimeDuration replaces the waiting time duration. You need to call a SendSignal() again to trigger a new timer with a new waiting time duration.
func (d *Debouncer) UpdateTimeDuration(newTimeDuration time.Duration) {
d.timeDuration = newTimeDuration
}
// Done returns a receive-only channel to notify the caller when the triggered func has been executed.
func (d *Debouncer) Done() <-chan struct{} {
if d.done == nil {
d.done = make(chan struct{})
}
return d.done
}