-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtask.go
131 lines (119 loc) · 3.29 KB
/
task.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
package modmake
import (
"context"
"fmt"
"sync/atomic"
"time"
)
// Task is a convenient way to make a function that satisfies the Runner interface, and allows for more flexible invocation options.
type Task func(ctx context.Context) error
// WithoutErr is a convenience function that allows passing a function that should never return an error and translating it to a Task.
// The returned Task will recover panics by returning them as errors.
func WithoutErr(fn func(context.Context)) Task {
return func(ctx context.Context) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("%v", r)
}
}()
if fn != nil {
fn(ctx)
}
return nil
}
}
// WithoutContext is a convenience function that handles the inbound context.Context in cases where it isn't needed.
// If the context is cancelled when this Task executes, then the context's error will be returned.
func WithoutContext(fn func() error) Task {
return func(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
if fn != nil {
return fn()
}
return nil
}
}
}
// Plain is a convenience function that translates a no-argument, no-return function into a Task, combining the logic of WithoutContext and WithoutErr.
func Plain(fn func()) Task {
return func(ctx context.Context) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("%v", r)
}
}()
select {
case <-ctx.Done():
return ctx.Err()
default:
if fn != nil {
fn()
}
return nil
}
}
}
// Error will create a Task returning an error, creating it by passing msg and args to [fmt.Errorf].
func Error(msg string, args ...any) Task {
return func(ctx context.Context) error {
return fmt.Errorf(msg, args...)
}
}
func (t Task) Run(ctx context.Context) error {
if t != nil {
return t(ctx)
}
return nil
}
// Then returns a Task that runs if this Task executed successfully.
func (t Task) Then(other Runner) Task {
return func(ctx context.Context) error {
if err := t.Run(ctx); err != nil {
return err
}
return other.Run(ctx)
}
}
// Catch runs the catch function if this Task returns an error.
func (t Task) Catch(catch func(error) Task) Task {
return func(ctx context.Context) error {
if err := t.Run(ctx); err != nil {
return catch(err).Run(ctx)
}
return nil
}
}
// Finally can be used to run a function after a [Task] executes, regardless whether it was successful.
//
// The given function will receive the error returned from the base [Task], and may be nil.
// If the given function returns a non-nil error, it will be returned from the produced function.
// Otherwise, the error from the underlying [Task] will be returned.
func (t Task) Finally(finally func(err error) error) Task {
return func(ctx context.Context) (terr error) {
defer func() {
if err := finally(terr); err != nil {
terr = err
}
}()
return t.Run(ctx)
}
}
func (t Task) Debounce(interval time.Duration) Task {
if interval <= time.Duration(0) {
panic(fmt.Sprintf("invalid debounce interval: %d", int64(interval)))
}
var bouncing atomic.Bool
return func(ctx context.Context) error {
ready := bouncing.CompareAndSwap(false, true)
if !ready {
return nil
}
time.AfterFunc(interval, func() {
bouncing.CompareAndSwap(true, false)
})
return t.Run(ctx)
}
}