diff --git a/README-CN.md b/README-CN.md index 3cec8d3..e430987 100644 --- a/README-CN.md +++ b/README-CN.md @@ -25,6 +25,9 @@ go get -u github.com/ghosind/go-async import "github.com/ghosind/go-async" ``` +> [!NOTE] +> 本工具集尚未稳定,后续版本可能会有不兼容的更改。 + ## 入门 下面的代码中,通过`All`函数并发执行函数直到全部执行完成,并返回它们的返回结果。 @@ -54,6 +57,7 @@ out, err := async.All(func (ctx context.Context) (int, error) { - [`All`](https://pkg.go.dev/github.com/ghosind/go-async#All) - [`AllCompleted`](https://pkg.go.dev/github.com/ghosind/go-async#AllCompleted) +- [`Fallback`](https://pkg.go.dev/github.com/ghosind/go-async#Fallback) - [`Forever`](https://pkg.go.dev/github.com/ghosind/go-async#Forever) - [`Parallel`](https://pkg.go.dev/github.com/ghosind/go-async#Parallel) - [`ParallelCompleted`](https://pkg.go.dev/github.com/ghosind/go-async#ParallelCompleted) diff --git a/README.md b/README.md index e254067..8485b01 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ And then, import the library into your own code. import "github.com/ghosind/go-async" ``` +> [!NOTE] > This library is not stable yet, anything may change in the later versions. ## Getting Started @@ -56,6 +57,7 @@ For all functions, you can use the `XXXWithContext` function (like `AllWithConte - [`All`](https://pkg.go.dev/github.com/ghosind/go-async#All) - [`AllCompleted`](https://pkg.go.dev/github.com/ghosind/go-async#AllCompleted) +- [`Fallback`](https://pkg.go.dev/github.com/ghosind/go-async#Fallback) - [`Forever`](https://pkg.go.dev/github.com/ghosind/go-async#Forever) - [`Parallel`](https://pkg.go.dev/github.com/ghosind/go-async#Parallel) - [`ParallelCompleted`](https://pkg.go.dev/github.com/ghosind/go-async#ParallelCompleted) diff --git a/fallback.go b/fallback.go new file mode 100644 index 0000000..82bc474 --- /dev/null +++ b/fallback.go @@ -0,0 +1,43 @@ +package async + +import "context" + +// Fallback tries to run the functions in order until one function does not panic or return an +// error. It returns nil if one function succeeds, or returns the last error if all functions fail. +// +// err := async.Fallback(func() error { +// return errors.New("first error") +// }, func() error { +// return errors.New("second error") +// }, func() error { +// return nil +// }, func() error { +// return errors.New("third error") +// }) +// // err: +func Fallback(fn AsyncFn, fallbacks ...AsyncFn) error { + return fallback(context.Background(), fn, fallbacks...) +} + +// FallbackWithContext tries to run the functions in order with the specified context until one +// function does not panic or return an error. It returns nil if one function succeeds, or returns +// the last error if all functions fail. +func FallbackWithContext(ctx context.Context, fn AsyncFn, fallbacks ...AsyncFn) error { + return fallback(ctx, fn, fallbacks...) +} + +// fallback runs the functions in order until one function does not panic or return an error. +func fallback(parent context.Context, fn AsyncFn, fallbacks ...AsyncFn) error { + funcs := append([]AsyncFn{fn}, fallbacks...) + ctx := getContext(parent) + var err error + + for _, fn := range funcs { + _, err = invokeAsyncFn(fn, ctx, nil) + if err == nil { + return nil + } + } + + return err +} diff --git a/fallback_test.go b/fallback_test.go new file mode 100644 index 0000000..009fd99 --- /dev/null +++ b/fallback_test.go @@ -0,0 +1,94 @@ +package async_test + +import ( + "context" + "errors" + "testing" + + "github.com/ghosind/go-assert" + "github.com/ghosind/go-async" +) + +func TestFallback(t *testing.T) { + a := assert.New(t) + runCount := 0 + + err := async.Fallback( + func() error { + runCount++ + return errors.New("first error") + }, + func() error { + runCount++ + return nil + }, + func() error { + runCount++ + return errors.New("last error") + }, + ) + a.NilNow(err) + a.EqualNow(runCount, 2) +} + +func TestFallbackAllFailed(t *testing.T) { + a := assert.New(t) + runCount := 0 + finalErr := errors.New("third error") + + err := async.Fallback( + func() error { + runCount++ + return errors.New("first error") + }, + func() error { + runCount++ + return errors.New("second error") + }, + func() error { + runCount++ + return finalErr + }, + ) + a.IsErrorNow(err, finalErr) + a.EqualNow(runCount, 3) +} + +func TestFallbackWithContext(t *testing.T) { + a := assert.New(t) + runCount := 0 + + err := async.FallbackWithContext( + context.Background(), + func() error { + runCount++ + return errors.New("first error") + }, + func() error { + runCount++ + return nil + }, + func() error { + runCount++ + return errors.New("last error") + }, + ) + a.NilNow(err) + a.EqualNow(runCount, 2) +} + +func ExampleFallback() { + err := async.Fallback(func() error { + return errors.New("first error") + }, func() error { + return errors.New("second error") + }, func() error { + return nil + }, func() error { + return errors.New("third error") + }) + if err != nil { + // handle the error + } + // err: +}