3
\$\begingroup\$

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!

\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.