func Spawn(N int, fn func(id int), whendone ...func()) {
	waiting := int32(N)
	for k := 0; k < N; k += 1 {
		go func(k int) {
			defer func() {
				if atomic.AddInt32(&waiting, -1) == 0 {
					for _, fn := range whendone {
						fn()
					}
				}
			}()
			fn(k)
		}(int(k))
	}
}
Spawns N routines, after each completes runs all whendone functions
package async
import (
	"fmt"
	"sync"
	"sync/atomic"
)
type Result struct {
	Done  <-chan struct{}
	Error <-chan error
}
func (r Result) Wait() []error {
	select {
	case <-r.Done:
		var errs []error
		for err := range r.Error {
			errs = append(errs, err)
		}
		if len(errs) > 0 {
			return errs
		}
		return nil
	}
}
func (r Result) WaitError() error {
	errs := r.Wait()
	if len(errs) == 0 {
		return nil
	}
	return fmt.Errorf("%v", errs)
}
func All(fns ...func() error) Result {
	done := make(chan struct{}, 1)
	errs := make(chan error, len(fns))
	waiting := int32(len(fns))
	for _, fn := range fns {
		go func(fn func() error) {
			defer func() {
				if atomic.AddInt32(&waiting, -1) == 0 {
					done <- struct{}{}
					close(errs)
					close(done)
				}
			}()
			if err := fn(); err != nil {
				errs <- err
			}
		}(fn)
	}
	if len(fns) == 0 {
		done <- struct{}{}
		close(errs)
		close(done)
	}
	return Result{done, errs}
}
func SpawnWithResult(N int, fn func(id int) error) Result {
	done := make(chan struct{}, 1)
	errs := make(chan error, N)
	waiting := int32(N)
	for id := 0; id < N; id++ {
		go func(id int) {
			defer func() {
				if atomic.AddInt32(&waiting, -1) == 0 {
					done <- struct{}{}
					close(errs)
					close(done)
				}
			}()
			if err := fn(id); err != nil {
				errs <- err
			}
		}(id)
	}
	if N == 0 {
		done <- struct{}{}
		close(errs)
		close(done)
	}
	return Result{done, errs}
}