Implementing Promise with Channels in Go

huangapple go评论84阅读模式
英文:

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

    // 在等待5秒的同时,是否可能在这里做其他事情?

    // 一旦Promise在5秒后完成,Result就可用了。
}

我如何实现以下功能:

  1. 运行一个goroutine,立即将Promise返回给主goroutine。
  2. 在等待Promise.ResultPromise.Error中的任何内容被发送时,异步执行主程序中的其他操作。
  3. 一旦有内容被发送,从goroutine中返回并使该通道可供读取。
英文:

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 &amp;Promise{
                Result: r,
                Error:  e,
        }
}

func main() {
        var p = NewPromise()

        go func(p *Promise) {
                time.Sleep(time.Duration(5)*time.Second)
                p.Result &lt;- &quot;done&quot;
        }(p)

        if &lt;- p.Result {
                fmt.Println(&lt;-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.

答案1

得分: 30

一个不使用通道的不同方法,使其稍微更快/更高效:

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

英文:

A different approach without using channels, which makes it a little bit faster / more efficient:

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

func NewPromise(f func() (string, error)) *Promise {
	p := &amp;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)
	}()
}

<kbd>playground</kbd>

答案2

得分: 6

有一篇名为《从事件到未来和承诺,再回到事件》的论文,作者是Martin Sulzmann(发表于2016年2月),涵盖了这个主题。摘要如下:

基于通道通信和未来/承诺的事件是强大但看似不同的并发编程概念。我们展示了一个概念可以用另一个概念来表达,而且只需要很少的努力。我们的结果提供了基于轻量级库的方法来实现事件和未来/承诺。实证结果表明我们的方法在实践中表现良好。

根据该论文,未来(futures)的实现如下:

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
}

而承诺(promises)的实现如下:

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
}

详细了解论文中的细节、组合子等内容,请阅读原文。

英文:

There's a paper called "From Events to Futures and Promises and back" by Martin Sulzmann (published in February 2016), which covers that topic. The abstract says:

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

According to the paper, futures look like this:

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 &lt;- c
		}
	}()

	return future
}

Whereas promises are implemented as follows:

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
}

Read up the paper for details, combinators and more.

答案3

得分: 1

我也正在尝试实现JavaScript的Promise :). 这是一个学习目的的项目。在这个实现中,我学习了Go语言的channel、select和goroutine。我认为这个小型库符合你的需求。

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

p.Then(func(data interface{}) interface{} {
    fmt.Printf("我得到的是 %v\n", data.(string))
    return nil
})

Await(p)

欢迎贡献,如果有人有更好的想法来实现Golang中的Promise。这是我的仓库

英文:

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(&quot;sonla&quot;)
})

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

Await(p)

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

答案4

得分: 0

有很多方法可以实现这个,但是我举个例子,我调整了NewPromise()函数,使其接受一个函数作为参数,该函数将接受结果和错误通道。然后,NewPromise方法使用这个函数初始化一个goroutine,并返回带有相同通道的promise,以供读取。如果调用.then方法,它基本上接受两个函数作为参数。一个函数将处理通过结果通道传递的类型(字符串),另一个函数将处理错误通道的结果类型(错误)。.then方法在一个goroutine中调用一个私有的.then()方法,以选择先发生的事件,即结果或错误,然后调用适用于每个结果的函数。

在这个例子中,我只使用了一个简单的ticker,它等待一秒钟,然后通过结果通道发送"hi"。

希望这给你提供了一种实现的思路。

GoLang Playground:
https://play.golang.org/p/xc1xvv7hRx

英文:

There are a ton of ways to do this, but what I've done for example is adjusted NewPromise() to take a function as an arg that will accept the result and error channels. Then the NewPromise method initializes a go routine with this function, returning the promise with those same channels to be read from. if you call the .Then method, this basically takes two functions as arguments. One that will handle the type that you are passing through the result channel (string) and one that handles the result type of the error channel (error). The .Then method then calls a private .then() method in a goroutine to select upon which happens first, either the result, or the error, then calls the function appropriate for each result.

For the example, I have only used a simple ticker that waits one second and then sends "hi" through the result channel.

I hope this gives you an idea of one way to do this.

GoLang Playground:
https://play.golang.org/p/xc1xvv7hRx

答案5

得分: -1

使用仅包含数据通道的"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 已经被解析,它会从一个关闭的通道中读取,并立即将解析后的值发送到处理程序通道。否则,它将等待直到 promise 被解析。

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

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

英文:

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

type Promise[A any] interface {
	Then(handler chan &lt;- A)	
}

type promiseImpl [A any] struct {	
	value A
	done &lt;- chan struct{}
}

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

func MakePromise[A any](resolver &lt;- chan A) Promise[A] {
	done := make(chan struct{})
	result := promiseImpl[A]{done: done}
	go func() {
		defer close(done)
		result.value = &lt;- 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.

huangapple
  • 本文由 发表于 2016年3月11日 04:05:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/35926173.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定