Hunch provides functions like: All
, First
, Retry
, Waterfall
etc., that makes asynchronous flow control more intuitive.
Go have several concurrency patterns, here're some articles:
- https://blog.golang.org/pipelines
- https://blog.golang.org/context
- https://blog.golang.org/go-concurrency-patterns-timing-out-and
- https://blog.golang.org/advanced-go-concurrency-patterns
But nowadays, using the context
package is the most powerful pattern.
So base on context
, Hunch provides functions that can help you deal with complex asynchronous logics with ease.
$ go get -u -v github.com/aaronjan/hunch
import "github.com/aaronjan/hunch"
$ go mod tidy
type Executable func(context.Context) (interface{}, error)
type ExecutableInSequence func(context.Context, interface{}) (interface{}, error)
func All(parentCtx context.Context, execs ...Executable) ([]interface{}, error)
All returns all the outputs from all Executables, order guaranteed.
ctx := context.Background()
r, err := hunch.All(
ctx,
func(ctx context.Context) (interface{}, error) {
time.Sleep(300 * time.Millisecond)
return 1, nil
},
func(ctx context.Context) (interface{}, error) {
time.Sleep(200 * time.Millisecond)
return 2, nil
},
func(ctx context.Context) (interface{}, error) {
time.Sleep(100 * time.Millisecond)
return 3, nil
},
)
fmt.Println(r, err)
// Output:
// [1 2 3] <nil>
func Take(parentCtx context.Context, num int, execs ...Executable) ([]interface{}, error)
Take returns the first num
values outputted by the Executables.
ctx := context.Background()
r, err := hunch.Take(
ctx,
// Only need the first 2 values.
2,
func(ctx context.Context) (interface{}, error) {
time.Sleep(300 * time.Millisecond)
return 1, nil
},
func(ctx context.Context) (interface{}, error) {
time.Sleep(200 * time.Millisecond)
return 2, nil
},
func(ctx context.Context) (interface{}, error) {
time.Sleep(100 * time.Millisecond)
return 3, nil
},
)
fmt.Println(r, err)
// Output:
// [3 2] <nil>
func Last(parentCtx context.Context, num int, execs ...Executable) ([]interface{}, error)
Last returns the last num
values outputted by the Executables.
ctx := context.Background()
r, err := hunch.Last(
ctx,
// Only need the last 2 values.
2,
func(ctx context.Context) (interface{}, error) {
time.Sleep(300 * time.Millisecond)
return 1, nil
},
func(ctx context.Context) (interface{}, error) {
time.Sleep(200 * time.Millisecond)
return 2, nil
},
func(ctx context.Context) (interface{}, error) {
time.Sleep(100 * time.Millisecond)
return 3, nil
},
)
fmt.Println(r, err)
// Output:
// [2 1] <nil>
func Waterfall(parentCtx context.Context, execs ...ExecutableInSequence) (interface{}, error)
Waterfall runs ExecutableInSequence
s one by one, passing previous result to next Executable as input. When an error occurred, it stop the process then returns the error. When the parent Context canceled, it returns the Err()
of it immediately.
ctx := context.Background()
r, err := hunch.Waterfall(
ctx,
func(ctx context.Context, n interface{}) (interface{}, error) {
return 1, nil
},
func(ctx context.Context, n interface{}) (interface{}, error) {
return n.(int) + 1, nil
},
func(ctx context.Context, n interface{}) (interface{}, error) {
return n.(int) + 1, nil
},
)
fmt.Println(r, err)
// Output:
// 3 <nil>
func Retry(parentCtx context.Context, retries int, fn Executable) (interface{}, error)
Retry attempts to get a value from an Executable instead of an Error. It will keeps re-running the Executable when failed no more than retries
times. Also, when the parent Context canceled, it returns the Err()
of it immediately.
count := 0
getStuffFromAPI := func() (int, error) {
if count == 5 {
return 1, nil
}
count++
return 0, fmt.Errorf("timeout")
}
ctx := context.Background()
r, err := hunch.Retry(
ctx,
10,
func(ctx context.Context) (interface{}, error) {
rs, err := getStuffFromAPI()
return rs, err
},
)
fmt.Println(r, err, count)
// Output:
// 1 <nil> 5