I try to implement the features of the js Promise using Go.
Currently, I had implemented Promise.all()
, Promise.allSettled()
and Promise.race()
static methods.
Code here:
package go_promise
import (
"sort"
)
type Promiser interface {
AllSettled(iterable []Workload) []Result
All(iterable []Workload) []Result
Race(iterable []Workload) *Result
}
type promise struct {
}
type Workload func(args ...interface{}) interface{}
type Result struct {
Idx int
R interface{}
}
func NewPromise() *promise {
return &promise{}
}
func (p *promise) AllSettled(iterable []Workload) []Result {
if len(iterable) == 0 {
return nil
}
c := make(chan struct{}, len(iterable))
r := make([]Result, len(iterable))
for idx, iter := range iterable {
iter := iter
idx := idx
go func() {
r[idx].Idx = idx
r[idx].R = iter()
c <- struct{}{}
}()
}
for i := 0; i < len(iterable); i++ {
<-c
}
sort.Slice(r, func(i, j int) bool {
return r[i].Idx < r[j].Idx
})
close(c)
return r
}
func (p *promise) All(iterable []Workload) []Result {
if len(iterable) == 0 {
return nil
}
c := make(chan Result, len(iterable))
errc := make(chan Result, 1)
r := []Result{}
for idx, iter := range iterable {
iter := iter
idx := idx
go func() {
result := iter()
if err, ok := result.(error); ok {
errc <- Result{Idx: idx, R: err}
} else {
c <- Result{Idx: idx, R: result}
}
}()
}
for {
select {
case result := <-errc:
return []Result{result}
case result := <-c:
r = append(r, result)
if len(r) == len(iterable) {
sort.Slice(r, func(i, j int) bool {
return r[i].Idx < r[j].Idx
})
return r
}
}
}
}
func (p *promise) Race(iterable []Workload) *Result {
if len(iterable) == 0 {
return nil
}
c := make(chan *Result, len(iterable))
for _, iter := range iterable {
iter := iter
go func() {
r := Result{R: iter()}
c <- &r
}()
}
return <-c
}
unit test:
package go_promise_test
import (
"errors"
"reflect"
"testing"
"time"
go_promise "github.com/mrdulin/golang/src/go-promise"
)
func workloadGen(i interface{}, options ...interface{}) go_promise.Workload {
return func(args ...interface{}) interface{} {
var sec time.Duration = 1
if len(options) != 0 {
sec = options[0].(time.Duration)
}
time.Sleep(time.Second * sec)
return i
}
}
func TestPromise_AllSettled(t *testing.T) {
t.Run("should settle all workloads and return results", func(t *testing.T) {
p := go_promise.NewPromise()
w1 := workloadGen(1)
w2 := workloadGen(2)
w3 := workloadGen(3)
ws := []go_promise.Workload{w1, w2, w3}
got := p.AllSettled(ws)
want := []go_promise.Result{{Idx: 0, R: 1}, {Idx: 1, R: 2}, {Idx: 2, R: 3}}
if !reflect.DeepEqual(got, want) {
t.Fatalf("got: %+v, want: %+v", got, want)
}
})
t.Run("should settle empty workloads and return nil", func(t *testing.T) {
p := go_promise.NewPromise()
ws := []go_promise.Workload{}
got := p.AllSettled(ws)
var want []go_promise.Result
if !reflect.DeepEqual(got, want) {
t.Fatalf("got: %#v, want: %#v", got, want)
}
})
}
func TestPromise_All(t *testing.T) {
t.Run("should handle all workloads", func(t *testing.T) {
p := go_promise.NewPromise()
w1 := workloadGen(1)
w2 := workloadGen(2)
w3 := workloadGen(3)
ws := []go_promise.Workload{w1, w2, w3}
got := p.All(ws)
want := []go_promise.Result{{Idx: 0, R: 1}, {Idx: 1, R: 2}, {Idx: 2, R: 3}}
if !reflect.DeepEqual(got, want) {
t.Fatalf("got: %#v, want: %#v", got, want)
}
})
t.Run("should reject the promise when any workload is rejected", func(t *testing.T) {
p := go_promise.NewPromise()
err := errors.New("network")
w1 := workloadGen(1)
w2 := workloadGen(2)
w3 := workloadGen(err)
ws := []go_promise.Workload{w1, w2, w3}
got := p.All(ws)
want := []go_promise.Result{{Idx: 2, R: err}}
if !reflect.DeepEqual(got, want) {
t.Fatalf("got: %+v, want: %+v", got, want)
}
})
}
func TestPromise_Race(t *testing.T) {
t.Run("should race all resolved promise", func(t *testing.T) {
p := go_promise.NewPromise()
var t1 time.Duration = 2
var t2 time.Duration = 1
w1 := workloadGen(1, t1)
w2 := workloadGen(2, t1)
w3 := workloadGen(3, t2)
ws := []go_promise.Workload{w1, w2, w3}
got := p.Race(ws)
want := &go_promise.Result{Idx: 0, R: 3}
if !reflect.DeepEqual(got, want) {
t.Fatalf("got: %+v, want: %+v", got, want)
}
})
t.Run("should race promises with a rejected promise", func(t *testing.T) {
p := go_promise.NewPromise()
err := errors.New("network")
var t1 time.Duration = 2
var t2 time.Duration = 1
w1 := workloadGen(1, t1)
w2 := workloadGen(2, t1)
w3 := workloadGen(err, t2)
ws := []go_promise.Workload{w1, w2, w3}
got := p.Race(ws)
want := &go_promise.Result{Idx: 0, R: err}
if !reflect.DeepEqual(got, want) {
t.Fatalf("got: %+v, want: %+v", got, want)
}
})
}
unit test result:
☁ go-promise [master] ⚡ go test -v -race
=== RUN TestPromise_AllSettled
=== RUN TestPromise_AllSettled/should_settle_all_workloads_and_return_results
=== RUN TestPromise_AllSettled/should_settle_empty_workloads_and_return_nil
--- PASS: TestPromise_AllSettled (1.00s)
--- PASS: TestPromise_AllSettled/should_settle_all_workloads_and_return_results (1.00s)
--- PASS: TestPromise_AllSettled/should_settle_empty_workloads_and_return_nil (0.00s)
=== RUN TestPromise_All
=== RUN TestPromise_All/should_handle_all_workloads
=== RUN TestPromise_All/should_reject_the_promise_when_any_workload_is_rejected
--- PASS: TestPromise_All (2.01s)
--- PASS: TestPromise_All/should_handle_all_workloads (1.00s)
--- PASS: TestPromise_All/should_reject_the_promise_when_any_workload_is_rejected (1.00s)
=== RUN TestPromise_Race
=== RUN TestPromise_Race/should_race_all_resolved_promise
=== RUN TestPromise_Race/should_race_promises_with_a_rejected_promise
--- PASS: TestPromise_Race (2.01s)
--- PASS: TestPromise_Race/should_race_all_resolved_promise (1.00s)
--- PASS: TestPromise_Race/should_race_promises_with_a_rejected_promise (1.00s)
PASS
ok github.com/mrdulin/golang/src/go-promise 5.354s
Although there is no data race condition problem, I am not sure if there are goroutine memory leaks, performance issues, and other issues that have not been considered. Please help me review my code, thanks!