-
-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathfunc.go
224 lines (190 loc) · 5.82 KB
/
func.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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package gogu
import (
"fmt"
"sync"
"time"
"github.com/esimov/gogu/cache"
"golang.org/x/exp/constraints"
)
// Flip creates a function that invokes fn with arguments reversed.
func Flip[T any](fn func(args ...T) []T) func(args ...T) []T {
return func(args ...T) []T {
return Reverse(fn(args...))
}
}
// Delay invokes the callback function with a predefined delay.
func Delay(delay time.Duration, fn func()) *time.Timer {
t := time.AfterFunc(delay, fn)
return t
}
// After creates a function wrapper that does nothing at first.
// From the nth call onwards, it starts actually invoking the callback function.
// Useful for grouping responses, where you need to be sure that all
// the calls have finished just before proceeding to the actual job.
func After[V constraints.Signed](n *V, fn func()) {
if *n < 1 {
fn()
}
*n-- // decrease the n as pointer receiver
}
// Before creates a function wrapper that memoizes its return value.
// From the nth call onwards, the memoized result of the last invocation is returned immediately
// instead of invoking function again. So the wrapper will invoke function at most n-1 times.
func Before[S ~string, T any, V constraints.Signed](n *V, c *cache.Cache[S, T], fn func() T) T {
var memo *cache.Item[T]
*n-- // decrease the n as pointer receiver
if *n > 0 {
return fn()
}
if *n == 0 {
c.Set("func", fn(), cache.DefaultExpiration)
}
memo, _ = c.Get("func")
return memo.Val()
}
// Once is like Before, but it's invoked only once.
// Repeated calls to the modified function will have no effect
// and the function invocation is returned from the cache.
func Once[S ~string, T comparable, V constraints.Signed](c *cache.Cache[S, T], fn func() T) T {
memo, _ := c.Get("func")
if memo == nil {
c.Set("func", fn(), cache.DefaultExpiration)
return fn()
}
memo, _ = c.Get("func")
return memo.Val()
}
// RType is a generic struct type used as method receiver on retry operations.
type RType[T any] struct {
Input T
}
// Retry tries to invoke the callback function `n` times.
// It runs until the number of attempts is reached or the returned value of the callback function is nil.
func (v RType[T]) Retry(n int, fn func(T) error) (int, error) {
var (
err error
attempt int
)
if n < 0 {
return attempt, fmt.Errorf("the number of attempts should be a positive number, got %v", n)
}
for attempt < n {
if err = fn(v.Input); err == nil {
return attempt, nil
}
attempt++
}
return attempt, err
}
// RetryWithDelay tries to invoke the callback function `n` times, but with a delay between each call.
// It runs until the number of attempts is reached or the error return value of the callback function is nil.
func (v RType[T]) RetryWithDelay(n int, delay time.Duration, fn func(time.Duration, T) error) (time.Duration, int, error) {
var (
err error
attempt int
)
start := time.Now()
for attempt < n {
err = fn(time.Since(start), v.Input)
if err == nil {
return time.Since(start), attempt, nil
}
<-time.After(delay)
attempt++
}
return time.Since(start), attempt, err
}
type debouncer struct {
mu sync.Mutex
timer *time.Timer
duration time.Duration
}
// NewDebounce creates a new debounced version of the invoked function which
// postpone the execution with a time delay passed in as a function argument.
// It returns a callback function which will be invoked after the predefined delay and
// also a cancel method which should be invoked to cancel a scheduled debounce.
func NewDebounce(wait time.Duration) (func(f func()), func()) {
d := &debouncer{duration: wait}
return func(f func()) {
d.add(f)
}, d.cancel
}
// add method schedules the execution of the passed in function after a predefined delay.
func (d *debouncer) add(f func()) {
d.mu.Lock()
defer d.mu.Unlock()
if d.timer != nil {
d.timer.Stop()
}
d.timer = time.AfterFunc(d.duration, f)
}
// cancel the execution of a scheduled debounce function.
func (d *debouncer) cancel() {
d.mu.Lock()
defer d.mu.Unlock()
if d.timer != nil {
d.timer.Stop()
d.timer = nil
}
}
// The throttle implementation is based on this package: https://github.com/boz/go-throttle.
type throttler struct {
last time.Time
cond *sync.Cond
duration time.Duration
waiting bool
trailing bool
stop bool
}
// NewThrottle creates a throttled function in order to limit the frequency rate at which the passed in function is invoked.
// The throttled function comes with a cancel method for canceling delayed function invocation.
// If the trailing parameter is true, the function is invoked right after the throttled code
// has been started, but at the trailing edge of the timeout.
// In this case the code will be executed one more time at the beginning of the next period.
//
// This function is useful for rate-limiting events that occur faster than you can keep up with.
func NewThrottle(wait time.Duration, trailing bool) *throttler {
t := &throttler{
cond: &sync.Cond{
L: new(sync.Mutex),
},
duration: wait,
trailing: trailing,
}
return t
}
// Call schedules the execution of the passed in function after the predefined delay.
func (t *throttler) Call() {
t.cond.L.Lock()
defer t.cond.L.Unlock()
if !t.waiting && !t.stop {
delta := time.Since(t.last)
if delta > t.duration {
t.waiting = true
t.cond.Broadcast()
} else if t.trailing {
t.waiting = true
time.AfterFunc(t.duration-delta, t.cond.Broadcast)
}
}
}
// Next returns true at most once per time period. It runs until the throttled function is not canceled.
func (t *throttler) Next() bool {
t.cond.L.Lock()
defer t.cond.L.Unlock()
for !t.waiting && !t.stop {
t.cond.Wait()
}
if !t.stop {
t.waiting = false
t.last = time.Now()
}
return !t.stop
}
// Cancel cancels the execution of a scheduled throttle function.
func (t *throttler) Cancel() {
t.cond.L.Lock()
defer t.cond.L.Unlock()
t.stop = true
t.cond.Broadcast()
}