Skip to content

Commit 01e6fb5

Browse files
committed
Tweak combining APIs
1 parent d3628cb commit 01e6fb5

File tree

16 files changed

+349
-115
lines changed

16 files changed

+349
-115
lines changed

.buildkite/pipeline.yaml

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,14 @@ steps:
44
commands:
55
- golangci-lint run --timeout 10m0s
66

7-
- label: ':hammer: Test'
7+
- label: ':hammer: Test (:codecov: + :codeclimate:)'
88
commands:
9-
- gotestsum --junitfile test.xml ./...
9+
- gotestsum --junitfile test.xml -- -race -coverprofile=cover.out ./...
10+
- sh .buildkite/upload_coverage.sh cover.out
1011
plugins:
11-
- test-collector#v1.10.0:
12+
- test-collector#v1.10.1:
1213
files: test.xml
1314
format: junit
1415
env:
1516
GOEXPERIMENT: rangefunc
1617

17-
- label: ':codecov: + :codeclimate: Coverage'
18-
commands:
19-
- go test -race -coverprofile=cover.out ./...
20-
- sh .buildkite/upload_coverage.sh cover.out
21-
env:
22-
GOEXPERIMENT: rangefunc

.codeclimate.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@ exclude_patterns:
1313
- "go.sum"
1414
- "LICENSE"
1515
- "nocopy.go"
16+
engines:
17+
golangci:
18+
enabled: true

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
- name: 🧸 golangci-lint
2727
uses: golangci/golangci-lint-action@v4
2828
with:
29-
version: v1.56.1
29+
version: v1.56.2
3030
- name: 🔨 Test
3131
run: go test -race ./...
3232
env:

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
[![Maintainability](https://api.codeclimate.com/v1/badges/12a77c18122e2d1e1f6b/maintainability)](https://codeclimate.com/github/fillmore-labs/promise/maintainability)
88
[![Go Report Card](https://goreportcard.com/badge/fillmore-labs.com/promise)](https://goreportcard.com/report/fillmore-labs.com/promise)
99
[![License](https://img.shields.io/github/license/fillmore-labs/promise)](https://www.apache.org/licenses/LICENSE-2.0)
10+
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ffillmore-labs%2Fpromise.svg?type=shield&issueType=license)](https://app.fossa.com/projects/git%2Bgithub.com%2Ffillmore-labs%2Fpromise?ref=badge_shield&issueType=license)
1011

1112
The `promise` package provides interfaces and utilities for writing asynchronous code in Go.
1213

combine.go

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,48 @@ package promise
1919
import (
2020
"context"
2121
"fmt"
22-
"runtime/trace"
22+
"reflect"
2323

2424
"fillmore-labs.com/promise/result"
2525
)
2626

27-
// List is a list of [Future], representing results of asynchronous tasks.
28-
type List[R any] []Future[R]
27+
// AnyFuture matches a [Future] of any type.
28+
type AnyFuture interface {
29+
reflect() reflect.Value
30+
}
31+
32+
// AwaitAll returns a function that yields the results of all futures.
33+
// If the context is canceled, it returns an error for the remaining futures.
34+
func AwaitAll[R any](ctx context.Context, futures ...Future[R]) func(yield func(int, result.Result[R]) bool) {
35+
i := newIterator(ctx, convertValue[R], futures)
2936

30-
// All returns a function that yields the results of all futures.
37+
return i.yieldTo
38+
}
39+
40+
// AwaitAllAny returns a function that yields the results of all futures.
3141
// If the context is canceled, it returns an error for the remaining futures.
32-
func (l List[R]) All(ctx context.Context) func(yield func(int, result.Result[R]) bool) {
33-
defer trace.StartRegion(ctx, "asyncSeq").End()
34-
s := newIterator(ctx, l)
42+
func AwaitAllAny(ctx context.Context, futures ...AnyFuture) func(yield func(int, result.Result[any]) bool) {
43+
i := newIterator(ctx, convertValueAny, futures)
44+
45+
return i.yieldTo
46+
}
3547

36-
return s.yieldTo
48+
// AwaitAllResults waits for all futures to complete and returns the results.
49+
// If the context is canceled, it returns early with errors for the remaining futures.
50+
func AwaitAllResults[R any](ctx context.Context, futures ...Future[R]) []result.Result[R] {
51+
return awaitAllResults(len(futures), AwaitAll(ctx, futures...))
3752
}
3853

39-
// AwaitAll waits for all futures to complete and returns the results.
54+
// AwaitAllResultsAny waits for all futures to complete and returns the results.
4055
// If the context is canceled, it returns early with errors for the remaining futures.
41-
func (l List[R]) AwaitAll(ctx context.Context) []result.Result[R] {
42-
results := make([]result.Result[R], len(l))
43-
l.All(ctx)(func(i int, r result.Result[R]) bool {
56+
func AwaitAllResultsAny(ctx context.Context, futures ...AnyFuture) []result.Result[any] {
57+
return awaitAllResults(len(futures), AwaitAllAny(ctx, futures...))
58+
}
59+
60+
func awaitAllResults[R any](n int, iter func(yield func(int, result.Result[R]) bool)) []result.Result[R] {
61+
results := make([]result.Result[R], n)
62+
63+
iter(func(i int, r result.Result[R]) bool {
4464
results[i] = r
4565

4666
return true
@@ -51,10 +71,21 @@ func (l List[R]) AwaitAll(ctx context.Context) []result.Result[R] {
5171

5272
// AwaitAllValues returns the values of completed futures.
5373
// If any future fails or the context is canceled, it returns early with an error.
54-
func (l List[R]) AwaitAllValues(ctx context.Context) ([]R, error) {
55-
results := make([]R, len(l))
74+
func AwaitAllValues[R any](ctx context.Context, futures ...Future[R]) ([]R, error) {
75+
return awaitAllValues(len(futures), AwaitAll(ctx, futures...))
76+
}
77+
78+
// AwaitAllValuesAny returns the values of completed futures.
79+
// If any future fails or the context is canceled, it returns early with an error.
80+
func AwaitAllValuesAny(ctx context.Context, futures ...AnyFuture) ([]any, error) {
81+
return awaitAllValues(len(futures), AwaitAllAny(ctx, futures...))
82+
}
83+
84+
func awaitAllValues[R any](n int, iter func(yield func(int, result.Result[R]) bool)) ([]R, error) {
85+
results := make([]R, n)
5686
var yieldErr error
57-
l.All(ctx)(func(i int, r result.Result[R]) bool {
87+
88+
iter(func(i int, r result.Result[R]) bool {
5889
if r.Err() != nil {
5990
yieldErr = fmt.Errorf("list AwaitAllValues result %d: %w", i, r.Err())
6091

@@ -70,13 +101,25 @@ func (l List[R]) AwaitAllValues(ctx context.Context) ([]R, error) {
70101

71102
// AwaitFirst returns the result of the first completed future.
72103
// If the context is canceled, it returns early with an error.
73-
func (l List[R]) AwaitFirst(ctx context.Context) (R, error) {
104+
func AwaitFirst[R any](ctx context.Context, futures ...Future[R]) (R, error) {
105+
return awaitFirst(AwaitAll(ctx, futures...))
106+
}
107+
108+
// AwaitFirstAny returns the result of the first completed future.
109+
// If the context is canceled, it returns early with an error.
110+
func AwaitFirstAny(ctx context.Context, futures ...AnyFuture) (any, error) {
111+
return awaitFirst(AwaitAllAny(ctx, futures...))
112+
}
113+
114+
func awaitFirst[R any](iter func(yield func(int, result.Result[R]) bool)) (R, error) {
74115
var v result.Result[R]
75-
l.All(ctx)(func(_ int, r result.Result[R]) bool {
116+
117+
iter(func(_ int, r result.Result[R]) bool {
76118
v = r
77119

78120
return false
79121
})
122+
80123
if v == nil {
81124
return *new(R), ErrNoResult
82125
}

combine_all_test.go

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,15 @@ func TestAll(t *testing.T) {
4242
}
4343

4444
for i, v := range values {
45-
value, err := v.value, v.err
46-
go promises[i].Do(func() (int, error) { return value, err })
45+
promises[i].Do(func() (int, error) { return v.value, v.err })
4746
}
4847

4948
ctx, cancel := context.WithCancel(context.Background())
5049
defer cancel()
5150

5251
// when
53-
var results [3]result.Result[int]
54-
for i, r := range futures.All(ctx) { //nolint:typecheck
52+
results := make([]result.Result[int], len(futures))
53+
for i, r := range promise.AwaitAll(ctx, futures...) { //nolint:typecheck
5554
results[i] = r
5655
}
5756

@@ -74,13 +73,50 @@ func TestAllEmpty(t *testing.T) {
7473
ctx, cancel := context.WithCancel(context.Background())
7574
defer cancel()
7675

77-
var futures promise.List[int]
78-
7976
// when
80-
allFutures := futures.All(ctx)
77+
allFutures := promise.AwaitAllResults[int](ctx)
8178

8279
// then
80+
assert.Zero(t, len(allFutures))
8381
for _, v := range allFutures { //nolint:typecheck
8482
t.Errorf("Invalid value %v", v)
8583
}
8684
}
85+
86+
func TestAnyAll(t *testing.T) {
87+
t.Parallel()
88+
89+
// given
90+
ctx, cancel := context.WithCancel(context.Background())
91+
defer cancel()
92+
93+
p1, f1 := promise.New[int]()
94+
p2, f2 := promise.New[string]()
95+
p3, f3 := promise.New[struct{}]()
96+
97+
p1.Resolve(1)
98+
p2.Resolve("test")
99+
p3.Resolve(struct{}{})
100+
101+
// when
102+
results := make([]result.Result[any], 3)
103+
for i, r := range promise.AwaitAllAny(ctx, f1, f2, f3) { //nolint:typecheck
104+
results[i] = r
105+
}
106+
107+
// then
108+
for i, r := range results {
109+
if assert.NoError(t, r.Err()) {
110+
switch i {
111+
case 0:
112+
assert.Equal(t, 1, r.Value())
113+
case 1:
114+
assert.Equal(t, "test", r.Value())
115+
case 2:
116+
assert.Equal(t, struct{}{}, r.Value())
117+
default:
118+
assert.Fail(t, "unexpected index")
119+
}
120+
}
121+
}
122+
}

0 commit comments

Comments
 (0)