Skip to content

Commit e44a478

Browse files
committed
feat: simple future
1 parent baefb68 commit e44a478

File tree

4 files changed

+166
-0
lines changed

4 files changed

+166
-0
lines changed

async/async.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package async
2+
3+
import (
4+
"context"
5+
"golang.org/x/sync/errgroup"
6+
)
7+
8+
type Future[T any] interface {
9+
Done() <-chan struct{}
10+
Get() (T, error)
11+
}
12+
13+
type future[T any] struct {
14+
done chan struct{}
15+
value T
16+
err error
17+
}
18+
19+
func (f *future[T]) Done() <-chan struct{} {
20+
return f.done
21+
}
22+
23+
func (f *future[T]) Get() (T, error) {
24+
<-f.done
25+
return f.value, f.err
26+
}
27+
28+
func Compute[V any](fn func() (V, error)) Future[V] {
29+
fut := &future[V]{done: make(chan struct{})}
30+
go func() {
31+
defer close(fut.done)
32+
v, err := fn()
33+
fut.value = v
34+
fut.err = err
35+
}()
36+
return fut
37+
}
38+
39+
func ComputeAll[K comparable, V any](ctx context.Context, fn func(context.Context, K) (V, error), keys ...K) Future[map[K]V] {
40+
fut := &future[map[K]V]{done: make(chan struct{})}
41+
42+
numKeys := len(keys)
43+
results := make([]V, numKeys)
44+
45+
g, gCtx := errgroup.WithContext(ctx)
46+
g.SetLimit(numKeys)
47+
go func() {
48+
defer close(fut.done)
49+
if err := g.Wait(); err != nil {
50+
fut.err = err
51+
return
52+
}
53+
54+
fut.value = make(map[K]V)
55+
for i, v := range results {
56+
fut.value[keys[i]] = v
57+
}
58+
}()
59+
60+
for i, k := range keys {
61+
index, key := i, k
62+
g.Go(func() error {
63+
v, err := fn(gCtx, key)
64+
if err != nil {
65+
return err
66+
}
67+
results[index] = v
68+
return nil
69+
})
70+
}
71+
return fut
72+
}

async/async_test.go

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package async
2+
3+
import (
4+
"context"
5+
"errors"
6+
"gotest.tools/v3/assert"
7+
"testing"
8+
"time"
9+
)
10+
11+
func Test_ComputeError(t *testing.T) {
12+
vCh := make(chan int)
13+
eCh := make(chan error)
14+
fut := Compute(func() (int, error) {
15+
select {
16+
case v := <-vCh:
17+
return v, nil
18+
case e := <-eCh:
19+
return 0, e
20+
}
21+
})
22+
23+
e := errors.New("closed")
24+
eCh <- e
25+
res, err := fut.Get()
26+
assert.Equal(t, res, 0)
27+
assert.ErrorIs(t, err, e)
28+
}
29+
30+
func Test_ComputeAll(t *testing.T) {
31+
fut := ComputeAll(context.Background(), func(ctx context.Context, s string) (int, error) {
32+
select {
33+
case <-ctx.Done():
34+
return 0, ctx.Err()
35+
default:
36+
}
37+
38+
time.Sleep(10 * time.Millisecond)
39+
return len(s), nil
40+
}, "a", "ab", "abc", "abcd")
41+
expected := map[string]int{
42+
"a": 1,
43+
"ab": 2,
44+
"abc": 3,
45+
"abcd": 4,
46+
}
47+
48+
result, err := fut.Get()
49+
assert.NilError(t, err)
50+
assert.DeepEqual(t, expected, result)
51+
}
52+
53+
func Test_ComputeAllDone(t *testing.T) {
54+
wait := make(chan struct{})
55+
fut := ComputeAll(context.Background(), func(ctx context.Context, s string) (int, error) {
56+
select {
57+
case <-ctx.Done():
58+
return 0, ctx.Err()
59+
case <-wait:
60+
return len(s), nil
61+
}
62+
}, "a", "ab", "abc", "abcd")
63+
64+
expected := map[string]int{
65+
"a": 1,
66+
"ab": 2,
67+
"abc": 3,
68+
"abcd": 4,
69+
}
70+
71+
closed := false
72+
for {
73+
select {
74+
case <-fut.Done():
75+
result, err := fut.Get()
76+
assert.NilError(t, err)
77+
assert.DeepEqual(t, expected, result)
78+
return
79+
default:
80+
if !closed {
81+
close(wait)
82+
closed = true
83+
}
84+
}
85+
}
86+
}
87+
88+
func zero[T any]() T {
89+
var t T
90+
return t
91+
}

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.18
44

55
require (
66
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
7+
golang.org/x/sync v0.1.0
78
gotest.tools/v3 v3.4.0
89
)
910

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
1313
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
1414
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1515
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
16+
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
17+
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1618
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1719
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1820
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

0 commit comments

Comments
 (0)