Implementing Promise with Channels in Go

Viewed 8940

I'm trying to implement Promise in Go which would be similar to that in Javascript.

type Promise struct {
        Result chan string
        Error  chan error
}

func NewPromise() (*Promise) {
        r := make(chan string, 1)
        e := make(chan error, 1)
        return &Promise{
                Result: r,
                Error:  e,
        }
}

func main() {
        var p = NewPromise()

        go func(p *Promise) {
                time.Sleep(time.Duration(5)*time.Second)
                p.Result <- "done"
        }(p)

        if <- p.Result {
                fmt.Println(<-p.Result)
        }

        // Is it possible to do something else here while wait for 5s?

        // Once Promise is fulfilled after 5s, the Result is available.
}

How do I do the following:

  1. Run a goroutine, which return Promise to the main goroutine right away.
  2. Asynchronously do something on the main routine while wait for anything to be sent to either Promise.Result or Promise.Error

  3. Once something is sent, return from the goroutine and make that channel available to be read.

5 Answers

I am also trying to implement javascript's promise :). It's a learning purpose project. In this implementation, I learn go's channel, select, goroutine. I think this small library meets your need.

p := New(func(resolve func(interface{}), reject func(error)) {
    resolve("sonla")
})

p.Then(func(data interface{}) interface{} {
    fmt.Printf("What I get is %v\n", data.(string))
    return nil
})

Await(p)

Welcome for contribution, if anyone has a better idea to implement promise in golang. Here is my repo

How about this implementation of a "promise" with just a data channel:

type Promise[A any] interface {
    Then(handler chan <- A) 
}

type promiseImpl [A any] struct {   
    value A
    done <- chan struct{}
}

func (p promiseImpl[A]) Then(handler chan <- A) {
    go func() {
        <-p.done
        handler <- p.value
    }()
}

func MakePromise[A any](resolver <- chan A) Promise[A] {
    done := make(chan struct{})
    result := promiseImpl[A]{done: done}
    go func() {
        defer close(done)
        result.value = <- resolver
    }()
    return result
}

The basic implementation idea is to exploit the fact that reading from a closed channel will never block.

So for each Then call we start a goroutine. If the promise is already resolved, it reads from a closed channel and immediately dispatches the resolved value to the handler channel. Else it will wait until the promise gets resolved.

The resolution process is another goroutine that waits for the resolved value to become available on a source channel, it then memoizes the values and closes the trigger channel, which will cause all waiting goroutines to run.

Note that no other synchronization primitives are required.

Related