在 Go 中使用 Channels 实现 Promise

Implementing Promise with Channels in Go

我正在尝试在 Go 中实现与 Javascript 中类似的 Promise。

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.
}

如何执行以下操作:

  1. 运行一个goroutine,其中returnPromise到主goroutine右边 离开。
  2. 在等待时在主例程上异步执行某些操作 发送给 Promise.ResultPromise.Error

  3. 的任何内容
  4. 一旦发送了一些东西,return 从 goroutine 中进行 频道可供阅读。

有很多方法可以做到这一点,但我所做的例如调整 NewPromise() 以将函数作为 arg 来接受结果和错误通道。然后 NewPromise 方法使用此函数初始化一个 go 例程,返回具有要从中读取的相同通道的承诺。如果你调用 .Then 方法,这基本上需要两个函数作为参数。一种将处理您通过结果通道(字符串)传递的类型,另一种处理错误通道的结果类型(错误)。 .Then 方法然后调用 goroutine 中的私有 .then() 方法以 select 首先发生结果或错误,然后调用适合每个结果的函数。

例如,我只使用了一个简单的自动收报机,它等待一秒钟,然后通过结果通道发送 "hi"。

我希望这能让您了解一种方法。

GoLang 游乐场: https://play.golang.org/p/xc1xvv7hRx

一种不使用通道的不同方法,这使得它更快/更有效:

type Promise struct {
    wg  sync.WaitGroup
    res string
    err error
}

func NewPromise(f func() (string, error)) *Promise {
    p := &Promise{}
    p.wg.Add(1)
    go func() {
        p.res, p.err = f()
        p.wg.Done()
    }()
    return p
}

func (p *Promise) Then(r func(string), e func(error)) {
    go func() {
        p.wg.Wait()
        if p.err != nil {
            e(p.err)
            return
        }
        r(p.res)
    }()
}

playground

有一篇名为 "From Events to Futures and Promises and back" by Martin Sulzmann 的论文(发表于 2016 年 2 月),其中涵盖了该主题。摘要说:

Events based on channel communications and futures/promises are powerful but seemingly different concepts for concurrent programming. We show that one concept can be expressed in terms of the other with surprisingly little effort. Our results offer light-weight library based approaches to implement events and futures/promises. Empirical results show that our approach works well in practice.

根据论文,期货看起来像这样:

type Comp struct {
    value interface{}
    ok    bool
}

type Future chan Comp

func future(f func() (interface{}, bool)) Future {
    future := make(chan Comp)

    go func() {
        v, o := f()
        c := Comp{v, o}
        for {
            future <- c
        }
    }()

    return future
}

而 promise 的实现如下:

type Promise struct {
    lock chan int
    ft   Future
    full bool
}

func promise() Promise {
    return Promise{make(chan int, 1), make(chan Comp), false}
}

func (pr Promise) future() Future {
    return pr.ft
}

阅读论文以了解详细信息、组合器等。

我也在努力实现javascript的承诺:)。这是一个学习目的项目。在这个实现中,我学习了go的channel,select,goroutine。我认为这个小型图书馆可以满足您的需要。

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)

欢迎贡献,如果有人有更好的想法在 golang 中实现承诺。这是我的 repo

仅使用数据通道实现“promise”怎么样:

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
}

基本的实现思路是利用从关闭的通道读取永远不会阻塞的事实。

因此,对于每个 Then 调用,我们都会启动一个 goroutine。如果 promise 已经解析,它会从关闭的通道中读取并立即将已解析的值分派给处理程序通道。否则它将等到承诺得到解决。

解析过程是另一个 goroutine,它等待已解析的值在源通道上可用,然后记忆这些值并关闭触发通道,这将导致所有等待的 goroutines 运行。

请注意,不需要其他同步原语。