diff --git a/README.md b/README.md index 9d37ed23..e975267e 100644 --- a/README.md +++ b/README.md @@ -257,6 +257,7 @@ Concurrency helpers: - [Synchronize](#synchronize) - [Async](#async) - [Transaction](#transaction) +- [WaitFor](#waitfor) Error handling: @@ -2837,6 +2838,38 @@ _, _ = transaction.Process(-5) // rollback 1 ``` +### WaitFor + +Runs periodically until a condition is validated. + +```go +alwaysTrue := func(i int) bool { return true } +alwaysFalse := func(i int) bool { return false } +laterTrue := func(i int) bool { + return i > 5 +} + +iterations, duration, ok := lo.WaitFor(alwaysTrue, 10*time.Millisecond, time.Millisecond) +// 1 +// 0ms +// true + +iterations, duration, ok := lo.WaitFor(alwaysFalse, 10*time.Millisecond, time.Millisecond) +// 10 +// 10ms +// false + +iterations, duration, ok := lo.WaitFor(laterTrue, 10*time.Millisecond, time.Millisecond) +// 7 +// 7ms +// true + +iterations, duration, ok := lo.WaitFor(laterTrue, 10*time.Millisecond, 5*time.Millisecond) +// 2 +// 10ms +// false +``` + ### Validate Helper function that creates an error when a condition is not met. diff --git a/concurrency.go b/concurrency.go index 31a62dde..95580661 100644 --- a/concurrency.go +++ b/concurrency.go @@ -1,6 +1,9 @@ package lo -import "sync" +import ( + "sync" + "time" +) type synchronize struct { locker sync.Locker @@ -93,3 +96,35 @@ func Async6[A, B, C, D, E, F any](f func() (A, B, C, D, E, F)) <-chan Tuple6[A, }() return ch } + +// WaitFor runs periodically until a condition is validated. +func WaitFor(condition func(i int) bool, maxDuration time.Duration, tick time.Duration) (int, time.Duration, bool) { + if condition(0) { + return 1, 0, true + } + + start := time.Now() + + timer := time.NewTimer(maxDuration) + ticker := time.NewTicker(tick) + + defer func() { + timer.Stop() + ticker.Stop() + }() + + i := 1 + + for { + select { + case <-timer.C: + return i, time.Since(start), false + case <-ticker.C: + if condition(i) { + return i + 1, time.Since(start), true + } + + i++ + } + } +} diff --git a/concurrency_test.go b/concurrency_test.go index ae65efdd..6c65b5ad 100644 --- a/concurrency_test.go +++ b/concurrency_test.go @@ -212,3 +212,47 @@ func TestAsyncX(t *testing.T) { } } } + +func TestWaitFor(t *testing.T) { + t.Parallel() + testWithTimeout(t, 100*time.Millisecond) + is := assert.New(t) + + alwaysTrue := func(i int) bool { return true } + alwaysFalse := func(i int) bool { return false } + + iter, duration, ok := WaitFor(alwaysTrue, 10*time.Millisecond, time.Millisecond) + is.Equal(1, iter) + is.Equal(time.Duration(0), duration) + is.True(ok) + iter, duration, ok = WaitFor(alwaysFalse, 10*time.Millisecond, 4*time.Millisecond) + is.Equal(3, iter) + is.InEpsilon(10*time.Millisecond, duration, float64(500*time.Microsecond)) + is.False(ok) + + laterTrue := func(i int) bool { + return i >= 5 + } + + iter, duration, ok = WaitFor(laterTrue, 10*time.Millisecond, time.Millisecond) + is.Equal(6, iter) + is.InEpsilon(6*time.Millisecond, duration, float64(500*time.Microsecond)) + is.True(ok) + iter, duration, ok = WaitFor(laterTrue, 10*time.Millisecond, 5*time.Millisecond) + is.Equal(2, iter) + is.InEpsilon(10*time.Millisecond, duration, float64(500*time.Microsecond)) + is.False(ok) + + counter := 0 + + alwaysFalse = func(i int) bool { + is.Equal(counter, i) + counter++ + return false + } + + iter, duration, ok = WaitFor(alwaysFalse, 10*time.Millisecond, time.Millisecond) + is.Equal(10, iter) + is.InEpsilon(10*time.Millisecond, duration, float64(500*time.Microsecond)) + is.False(ok) +}