Skip to content

Commit 4621b56

Browse files
authored
after both, actionToFunc (#21)
* try afterBoth * update after_both * ActionToFunc * more tweaks * remove afterAll, ready to merge to main * code coverage * update comments
1 parent f9a3b6f commit 4621b56

6 files changed

+150
-0
lines changed

after_both.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package asynctask
2+
3+
import "context"
4+
5+
// AfterBothFunc is a function that has 2 input.
6+
type AfterBothFunc[T, S, R any] func(context.Context, *T, *S) (*R, error)
7+
8+
// AfterBoth runs the function after both 2 input task finished, and will be fed with result from 2 input task.
9+
// if one of the input task failed, the AfterBoth task will be failed and returned, even other one are still running.
10+
func AfterBoth[T, S, R any](ctx context.Context, tskT *Task[T], tskS *Task[S], next AfterBothFunc[T, S, R]) *Task[R] {
11+
return Start(ctx, func(fCtx context.Context) (*R, error) {
12+
t, err := tskT.Result(fCtx)
13+
if err != nil {
14+
return nil, err
15+
}
16+
17+
s, err := tskS.Result(fCtx)
18+
if err != nil {
19+
return nil, err
20+
}
21+
22+
return next(fCtx, t, s)
23+
})
24+
}
25+
26+
// AfterBothActionToFunc convert a Action to Func (C# term), to satisfy the AfterBothFunc interface.
27+
// Action is function that runs without return anything
28+
// Func is function that runs and return something
29+
func AfterBothActionToFunc[T, S any](action func(context.Context, *T, *S) error) func(context.Context, *T, *S) (*interface{}, error) {
30+
return func(ctx context.Context, t *T, s *S) (*interface{}, error) {
31+
return nil, action(ctx, t, s)
32+
}
33+
}

after_both_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package asynctask_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/Azure/go-asynctask"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func summarize2CountingTask(ctx context.Context, result1, result2 *int) (*int, error) {
13+
t := ctx.Value(testContextKey).(*testing.T)
14+
t.Logf("result1: %d", result1)
15+
t.Logf("result2: %d", result2)
16+
sum := *result1 + *result2
17+
t.Logf("sum: %d", sum)
18+
return &sum, nil
19+
}
20+
21+
func TestAfterBoth(t *testing.T) {
22+
t.Parallel()
23+
ctx := newTestContext(t)
24+
t1 := asynctask.Start(ctx, getCountingTask(10, "afterboth.P1", 20*time.Millisecond))
25+
t2 := asynctask.Start(ctx, getCountingTask(10, "afterboth.P2", 20*time.Millisecond))
26+
t3 := asynctask.AfterBoth(ctx, t1, t2, summarize2CountingTask)
27+
28+
sum, err := t3.Result(ctx)
29+
assert.NoError(t, err)
30+
assert.Equal(t, asynctask.StateCompleted, t3.State(), "Task should complete with no error")
31+
assert.Equal(t, *sum, 18, "Sum should be 18")
32+
}
33+
34+
func TestAfterBothFailureCase(t *testing.T) {
35+
t.Parallel()
36+
ctx := newTestContext(t)
37+
errTask := asynctask.Start(ctx, getErrorTask("devide by 0", 10*time.Millisecond))
38+
countingTask := asynctask.Start(ctx, getCountingTask(10, "afterboth.P1", 20*time.Millisecond))
39+
40+
task1Err := asynctask.AfterBoth(ctx, errTask, countingTask, summarize2CountingTask)
41+
_, err := task1Err.Result(ctx)
42+
assert.Error(t, err)
43+
44+
task2Err := asynctask.AfterBoth(ctx, errTask, countingTask, summarize2CountingTask)
45+
_, err = task2Err.Result(ctx)
46+
assert.Error(t, err)
47+
48+
task3NoErr := asynctask.AfterBoth(ctx, countingTask, countingTask, summarize2CountingTask)
49+
_, err = task3NoErr.Result(ctx)
50+
assert.NoError(t, err)
51+
}
52+
53+
func TestAfterBothActionToFunc(t *testing.T) {
54+
t.Parallel()
55+
ctx := newTestContext(t)
56+
57+
countingTask1 := asynctask.Start(ctx, getCountingTask(10, "afterboth.P1", 20*time.Millisecond))
58+
countingTask2 := asynctask.Start(ctx, getCountingTask(10, "afterboth.P2", 20*time.Millisecond))
59+
t2 := asynctask.AfterBoth(ctx, countingTask1, countingTask2, asynctask.AfterBothActionToFunc(func(ctx context.Context, result1, result2 *int) error {
60+
t := ctx.Value(testContextKey).(*testing.T)
61+
t.Logf("result1: %d", result1)
62+
t.Logf("result2: %d", result2)
63+
sum := *result1 + *result2
64+
t.Logf("sum: %d", sum)
65+
return nil
66+
}))
67+
_, err := t2.Result(ctx)
68+
assert.NoError(t, err)
69+
}

continue_with.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,12 @@ func ContinueWith[T any, S any](ctx context.Context, tsk *Task[T], next Continue
1414
return next(fCtx, result)
1515
})
1616
}
17+
18+
// ContinueActionToFunc convert a Action to Func (C# term), to satisfy the AsyncFunc interface.
19+
// Action is function that runs without return anything
20+
// Func is function that runs and return something
21+
func ContinueActionToFunc[T any](action func(context.Context, *T) error) func(context.Context, *T) (*interface{}, error) {
22+
return func(ctx context.Context, t *T) (*interface{}, error) {
23+
return nil, action(ctx, t)
24+
}
25+
}

continue_with_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package asynctask_test
22

33
import (
44
"context"
5+
"fmt"
56
"testing"
67
"time"
78

@@ -75,3 +76,18 @@ func TestContinueWithFailureCase(t *testing.T) {
7576
assert.Equal(t, asynctask.StateFailed, t3.State(), "Task3 should fail since preceeding task failed")
7677
assert.Equal(t, "devide by 0", err.Error())
7778
}
79+
80+
func TestContinueActionToFunc(t *testing.T) {
81+
t.Parallel()
82+
ctx := newTestContext(t)
83+
84+
t1 := asynctask.Start(ctx, func(ctx context.Context) (*int, error) { i := 0; return &i, nil })
85+
t2 := asynctask.ContinueWith(ctx, t1, asynctask.ContinueActionToFunc(func(ctx context.Context, i *int) error {
86+
if *i != 0 {
87+
return fmt.Errorf("input should be 0, but got %d", i)
88+
}
89+
return nil
90+
}))
91+
_, err := t2.Result(ctx)
92+
assert.NoError(t, err)
93+
}

task.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ import (
1111
// AsyncFunc is a function interface this asyncTask accepts.
1212
type AsyncFunc[T any] func(context.Context) (*T, error)
1313

14+
// ActionToFunc convert a Action to Func (C# term), to satisfy the AsyncFunc interface.
15+
// Action is function that runs without return anything
16+
// Func is function that runs and return something
17+
func ActionToFunc(action func(context.Context) error) func(context.Context) (*interface{}, error) {
18+
return func(ctx context.Context) (*interface{}, error) {
19+
return nil, action(ctx)
20+
}
21+
}
22+
1423
// Task is a handle to the running function.
1524
// which you can use to wait, cancel, get the result.
1625
type Task[T any] struct {

task_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,20 @@ func TestCompletedGenericTask(t *testing.T) {
150150
assert.Equal(t, *resultGet, result)
151151
}
152152

153+
func TestActionToFunc(t *testing.T) {
154+
t.Parallel()
155+
156+
action := func(ctx context.Context) error {
157+
return nil
158+
}
159+
160+
ctx := context.Background()
161+
task := asynctask.Start(ctx, asynctask.ActionToFunc(action))
162+
result, err := task.Result(ctx)
163+
assert.NoError(t, err)
164+
assert.Nil(t, result)
165+
}
166+
153167
func TestCrazyCaseGeneric(t *testing.T) {
154168
t.Parallel()
155169
ctx, cancelFunc := newTestContextWithTimeout(t, 3*time.Second)

0 commit comments

Comments
 (0)