Migrating from v1
Migrating from v1 to v2
v2 keeps the three functions and what they do. They are now generic, so results come back typed
instead of as interface{}, and a few edge cases behave differently: they are listed below.
1. Update the import path
go get github.com/vardius/gollback/v2import "github.com/vardius/gollback/v2"v2 needs Go 1.26 or newer.
2. Before and after
// v1res, err := gollback.Race(ctx, func(ctx context.Context) (interface{}, error) { return primary.User(ctx, id) }, func(ctx context.Context) (interface{}, error) { return replica.User(ctx, id) },)if err != nil { return err}user := res.(User)// v2user, err := gollback.Race(ctx, func(ctx context.Context) (User, error) { return primary.User(ctx, id) }, func(ctx context.Context) (User, error) { return replica.User(ctx, id) },)if err != nil { return err}The type parameter is inferred from the functions, so calls don’t name it. A slice of functions does:
[]gollback.AsyncFunc[User].
3. Every call
| v1 | v2 |
|---|---|
type AsyncFunc func(ctx context.Context) (interface{}, error) |
type AsyncFunc[T any] func(ctx context.Context) (T, error) |
Race(ctx, fns...) (interface{}, error) |
Race(ctx, fns...) (T, error) |
All(ctx, fns...) ([]interface{}, []error) |
All(ctx, fns...) ([]T, []error) |
Retry(ctx, retries, fn) (interface{}, error) |
Retry(ctx, attempts, fn) (T, error) |
ErrNoCallbacks |
ErrNoCallbacks |
Functions that returned different types into one call need a common type in v2: an interface they all
implement, or any if you really want v1’s behaviour back.
4. Behaviour changes
- Race, when every function fails, returns all of their errors joined with
errors.Join, in the order of the functions. v1 returned one of them. Check for a particular error witherrors.Isorerrors.As. - Race, when ctx is done first, returns ctx’s error joined with the errors of the functions that had
failed by then.
errors.Is(err, context.Canceled)still works; comparing with==doesn’t once one has failed. If ctx is done before the call, v2 calls no function. - Retry, when ctx is done, returns ctx’s error joined with the function’s last error, so you can see why
the attempts failed.
errors.Is(err, context.DeadlineExceeded)still works; comparing with==doesn’t. If ctx is done before the first call, the error is ctx’s alone, as before. - A failed call’s result is dropped. v1’s Retry returned the last attempt’s result beside its error, and v1’s All kept whatever a failed function returned. v2 returns the zero value with every error.
- All runs the first function on the calling goroutine, and the others on goroutines of their own, as before. A panic in the first function now reaches the caller, where it can be recovered.
- A nil context is no longer checked. v1 panicked with “nil context provided”. v2’s Race and Retry
panic on it, and All hands it to your functions. Pass
context.Background()orcontext.TODO()instead;go vetand staticcheck flag a nil context.