Skip to content

Commit 4589f64

Browse files
committed
feat: add All and AllWithContext.
1 parent 02077ab commit 4589f64

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

all.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package async
2+
3+
import (
4+
"context"
5+
"errors"
6+
)
7+
8+
func All(funcs ...func(context.Context) error) error {
9+
return allWithContext(context.Background(), funcs...)
10+
}
11+
12+
func AllWithContext(ctx context.Context, funcs ...func(context.Context) error) error {
13+
return allWithContext(ctx, funcs...)
14+
}
15+
16+
func allWithContext(parent context.Context, funcs ...func(context.Context) error) error {
17+
if len(funcs) == 0 {
18+
return nil
19+
}
20+
21+
ctx, canFunc := context.WithCancel(parent)
22+
errCh := make(chan error)
23+
retCh := make(chan struct{}, len(funcs))
24+
25+
defer canFunc()
26+
defer close(errCh)
27+
defer close(retCh)
28+
29+
for i := 0; i < len(funcs); i++ {
30+
fn := funcs[i]
31+
go func() {
32+
childCtx, childCanFunc := context.WithCancel(ctx)
33+
defer childCanFunc()
34+
35+
err := fn(childCtx)
36+
select {
37+
case <-ctx.Done():
38+
return
39+
default:
40+
if err != nil {
41+
errCh <- err
42+
} else {
43+
retCh <- struct{}{}
44+
}
45+
}
46+
}()
47+
}
48+
49+
finished := 0
50+
for {
51+
select {
52+
case <-parent.Done():
53+
return errors.New("context canceled")
54+
case err := <-errCh:
55+
return err
56+
case <-retCh:
57+
finished++
58+
}
59+
60+
if finished == len(funcs) {
61+
return nil
62+
}
63+
}
64+
}

all_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package async_test
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
"time"
8+
9+
"github.com/ghosind/go-assert"
10+
"github.com/ghosind/go-async"
11+
)
12+
13+
func TestAllWithoutFuncs(t *testing.T) {
14+
a := assert.New(t)
15+
16+
err := async.All()
17+
a.NilNow(err)
18+
}
19+
20+
func TestAllSuccess(t *testing.T) {
21+
a := assert.New(t)
22+
23+
data := make([]bool, 5)
24+
funcs := make([]func(context.Context) error, 0, 5)
25+
for i := 0; i < 5; i++ {
26+
n := i
27+
funcs = append(funcs, func(ctx context.Context) error {
28+
time.Sleep(time.Duration(n*100) * time.Millisecond)
29+
data[n] = true
30+
return nil
31+
})
32+
}
33+
34+
err := async.All(funcs...)
35+
a.NilNow(err)
36+
a.EqualNow(data, []bool{true, true, true, true, true})
37+
}
38+
39+
func TestAllFailure(t *testing.T) {
40+
a := assert.New(t)
41+
42+
data := make([]bool, 5)
43+
funcs := make([]func(context.Context) error, 0, 5)
44+
for i := 0; i < 5; i++ {
45+
n := i
46+
funcs = append(funcs, func(ctx context.Context) error {
47+
time.Sleep(time.Duration(n*100) * time.Millisecond)
48+
if n == 2 {
49+
return errors.New("n = 2")
50+
}
51+
data[n] = true
52+
return nil
53+
})
54+
}
55+
56+
err := async.All(funcs...)
57+
a.NotNilNow(err)
58+
a.EqualNow(err.Error(), "n = 2")
59+
a.EqualNow(data, []bool{true, true, false, false, false})
60+
}
61+
62+
func TestAllWithTimeoutedContext(t *testing.T) {
63+
a := assert.New(t)
64+
65+
data := make([]bool, 5)
66+
funcs := make([]func(context.Context) error, 0, 5)
67+
for i := 0; i < 5; i++ {
68+
n := i
69+
funcs = append(funcs, func(ctx context.Context) error {
70+
time.Sleep(time.Duration(n*100) * time.Millisecond)
71+
data[n] = true
72+
return nil
73+
})
74+
}
75+
76+
ctx, canFunc := context.WithTimeout(context.Background(), 150*time.Millisecond)
77+
defer canFunc()
78+
79+
err := async.AllWithContext(ctx, funcs...)
80+
a.NotNilNow(err)
81+
a.Equal(err.Error(), "context canceled")
82+
a.EqualNow(data, []bool{true, true, false, false, false})
83+
}

0 commit comments

Comments
 (0)