Comparison to Our Original Approach

… continued from the previous post.

Refactor Our Original Approach

What we have done to the previously can also be done to the approach using an error channel:

 1package main
 2
 3import "context"
 4
 5type Group struct {
 6	errc   chan error
 7	cancel context.CancelCauseFunc
 8	count  int
 9}
10
11func NewGroup(cancel context.CancelCauseFunc) *Group {
12	return &Group{errc: make(chan error, 1), cancel: cancel}
13}
14
15func (g *Group) Go(f func() error) {
16	g.count++
17	go func() {
18		g.errc <- f()
19	}()
20}
21
22func (g *Group) Wait() error {
23	var err error
24	for range g.count {
25		if e := <-g.errc; e != nil && err == nil {
26			err = e
27			g.cancel(e)
28		}
29	}
30
31	return err
32}

Making our function:

 1package main
 2
 3import (
 4	"context"
 5
 6	"fillmore-labs.com/blog/structured/pkg/task"
 7)
 8
 9func doWork(ctx context.Context) error {
10	ctx, cancel := context.WithCancelCause(ctx)
11	defer cancel(nil)
12
13	g := NewGroup(cancel)
14
15	g.Go(func() error {
16		return task.Task(ctx, "task1", processingTime/3, nil)
17	})
18
19	g.Go(func() error {
20		return task.Task(ctx, "task2", processingTime/2, errFail)
21	})
22
23	g.Go(func() error {
24		return task.Task(ctx, "task3", processingTime, nil)
25	})
26
27	return g.Wait()
28}

As we see we get a nearly identical result for the main function, witht the API neatly abstracting our soulution. One difference is that we have to call all subtasks asynchronously, since we need Group.Wait working on the error channel.

Summary

We have seen two approaches to structured concurrency with nearly identical APIs.

… continued in the next post.