|
| 1 | +package async |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "reflect" |
| 6 | +) |
| 7 | + |
| 8 | +// Until repeatedly calls the function until the test function returns true. A valid test function |
| 9 | +// must match the following requirements. |
| 10 | +// |
| 11 | +// - The first return value of the test function must be a boolean value. |
| 12 | +// - The parameters' number of the test function must be equal to the return values' number of the |
| 13 | +// execution function (exclude context). |
| 14 | +// - The parameters' types of the test function must be the same or convertible to the return |
| 15 | +// values' types of the execution function. |
| 16 | +// |
| 17 | +// c := 0 |
| 18 | +// Until(func() bool { |
| 19 | +// return c == 5 |
| 20 | +// }, func() { |
| 21 | +// c++ |
| 22 | +// }) |
| 23 | +func Until(testFn, fn AsyncFn) ([]any, error) { |
| 24 | + return until(context.Background(), testFn, fn) |
| 25 | +} |
| 26 | + |
| 27 | +// UntilWithContext repeatedly calls the function with the specified context until the test |
| 28 | +// function returns true. |
| 29 | +func UntilWithContext(ctx context.Context, testFn, fn AsyncFn) ([]any, error) { |
| 30 | + return until(ctx, testFn, fn) |
| 31 | +} |
| 32 | + |
| 33 | +// until repeatedly calls the function until the test function returns true. |
| 34 | +func until(parent context.Context, testFn, fn AsyncFn) ([]any, error) { |
| 35 | + validateUntilFuncs(testFn, fn) |
| 36 | + |
| 37 | + ctx := getContext(parent) |
| 38 | + |
| 39 | + for { |
| 40 | + out, err := invokeAsyncFn(fn, ctx, nil) |
| 41 | + |
| 42 | + testOut, _ := invokeAsyncFn(testFn, ctx, out) |
| 43 | + isDone := testOut[0].(bool) |
| 44 | + if isDone { |
| 45 | + return out, err |
| 46 | + } |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +// validateUntilFuncs validates the test function and the execution function. |
| 51 | +func validateUntilFuncs(testFn, fn AsyncFn) { |
| 52 | + if testFn == nil || fn == nil { |
| 53 | + panic(ErrNotFunction) |
| 54 | + } |
| 55 | + tft := reflect.TypeOf(testFn) // reflect.Type of the test function |
| 56 | + ft := reflect.TypeOf(fn) // reflect.Type of the function |
| 57 | + if tft.Kind() != reflect.Func || ft.Kind() != reflect.Func { |
| 58 | + panic(ErrNotFunction) |
| 59 | + } |
| 60 | + |
| 61 | + if tft.NumOut() <= 0 || tft.Out(0).Kind() != reflect.Bool { |
| 62 | + panic(ErrInvalidTestFunc) |
| 63 | + } |
| 64 | + |
| 65 | + ii := 0 // index of the test function input parameters list |
| 66 | + oi := 0 // index of the function return values list |
| 67 | + numIn := tft.NumIn() |
| 68 | + isTakeContext := isFuncTakesContext(tft) |
| 69 | + if isTakeContext { |
| 70 | + numIn-- |
| 71 | + ii++ |
| 72 | + } |
| 73 | + if numIn != ft.NumOut() { |
| 74 | + panic(ErrInvalidTestFunc) |
| 75 | + } |
| 76 | + |
| 77 | + for oi < numIn { |
| 78 | + it := tft.In(ii) // type of the value in the test function input parameters list |
| 79 | + ot := ft.Out(oi) // type of the value in the function return values list |
| 80 | + |
| 81 | + if it != ot && !it.ConvertibleTo(ot) { |
| 82 | + panic(ErrInvalidTestFunc) |
| 83 | + } |
| 84 | + |
| 85 | + ii++ |
| 86 | + oi++ |
| 87 | + } |
| 88 | +} |
0 commit comments