使用Golang实现Futures 和 Promises
WildaALU
8年前
<p>其它语言中Future和Promise的概念大量存在, 比如Node.js、Scala、Java、C#、C++ 11、Scheme、Swift等,可以方便的实现异步执行和回调。但是在Go语言的世界里,我们是通过goroutine/channel实现这种类似的功能呢,goroutine之间可以通过channel进行通讯, 但是,如果我们还是想使用Future/Promise的功能的话,该如何实现呢?</p> <blockquote> <p>Future,Promise或Delay是用于并发编程的一种设计模式。它们表示一个对象,这个对象用来作为一次计算结果的代理,而该结果开始的时候是未知的,因为计算还没有完成。Promise与Future的区别在于,Future是Promise的一个只读的视图,也就是说Future没有设置任务结果的方法,只能获取任务执行结果或者为Future添加回调函数。</p> </blockquote> <p>下面演示了实现一个Go Future的实现。因为Go目前还没有泛型的概念,所以为了使代码更通用,我会使用<code>interface{}</code>代表一个通用的对象。</p> <p>首先定义一个<code>Future</code>结构,</p> <p> </p> <p><code>go type Future struct { result interface{} //计算结果 err error //错误 signal chan struct{} //等待完成的信号 IsCompleted bool //计算是否完成 }</code></p> <p>这个Future可以执行下面的计算<code>func() (interface{}, error)</code>,所以计算方法应该实现为<code>FutureFunc</code>类型的函数,它会返回结果或者返回error:<br> <code>go type FutureFunc func() (interface{}, error)</code></p> <p>然后定义获取结果的<code>Get</code>和<code>GetOrTimeout</code>方法,它会阻塞直到获得结果或者超时(GetOrTimeout)。<br> `go<br> // GetOrTimeout is similar to Get(), but GetOrTimeout will not block after timeout.<br> func (f *Future) GetOrTimeout(d time.Duration) (result interface{}, err error, timeout bool) {<br> select {<br> case <-time.After(d):<br> return nil, nil, true<br> case <-f.signal:<br> return f.result, f.err, false<br> }<br> }</p> <p>// Get is used to get future result<br> func (f *Future) Get() (result interface{}, err error) {<br> <-f.signal<br> return f.result, f.err<br> }<br> `</p> <p>然后定义<code>NewFuture</code>就可以了:<br> `go<br> func NewFuture(fun FutureFunc) *Future {<br> f := new(Future)</p> <pre> <code>f.signal = make(chan struct{}, 1) go func() { defer close(f.signal) result, err := fun() f.result = result f.err = err f.IsCompleted = true }() return f </code></pre> <p>}<br> `</p> <p>一个使用的例子:<br> `go<br> func ExampleRequestFuture() {<br> requestFunc := func() (body interface{}, err error) {<br> url := "<a href="/misc/goto?guid=4959631892275664639" rel="external">http://www.baidu.com</a>"<br> var resp *http.Response<br> resp, err = http.Get(url)<br> if err != nil {<br> return<br> }<br> defer resp.Body.Close()<br> bodyBytes, err := ioutil.ReadAll(resp.Body)<br> return string(bodyBytes), err<br> }</p> <pre> <code>requestFuture := NewFuture(FutureFunc(requestFunc)) body, err, timeout := requestFuture.GetOrTimeout(10 * time.Second) if timeout { fmt.Println("timeout") } else { if err != nil { fmt.Printf("error: %v\n", err) } else { fmt.Printf("body: %v\n", body) } } </code></pre> <p>}<br> `</p> <p>如果你是一个Java程序了,可以发现这个Future类似Java中的<a href="/misc/goto?guid=4959668564172588010" rel="external">Future接口</a>。</p> <p>当然这个Future实现的还是非常的简陋,至少还应该实现回调接口比如<code>OnSuccess</code>、<code>OnFailure</code>、<code>OnComplete</code>等方法,另外一些方法如<code>Cancel</code>也应该加上。<br> 为了组合多个Future,避免掉入"回调陷阱",还应该实现Future的组合方法。</p> <p>为了实现<code>SetResult</code>和<code>SetError</code>的功能,可以实现一个类似的<code>Promise</code>的功能。</p> <p>但是,目前我不会去实现这个功能,一是目前我没有这方面的需求,而是 <a href="/misc/goto?guid=4959670728634972281" rel="external">@fanliao</a>已经实现了这样的一个框架,名字叫<a href="/misc/goto?guid=4959670728730954852" rel="external">go-promise</a>,代码放在了github上,我们不必再重复造轮子了。</p> <p>这个框架提供了丰富的功能:</p> <ul> <li> <p>Future and Promise</p> <ul> <li><code>NewPromise()</code></li> <li><code>promise.Future</code></li> </ul> </li> <li> <p>Promise and Future callbacks</p> <ul> <li><code>.OnSuccess(v interface{})</code></li> <li><code>.OnFailure(v interface{})</code></li> <li><code>.OnComplete(v interface{})</code></li> <li><code>.OnCancel()</code></li> </ul> </li> <li> <p>Get the result of future</p> <ul> <li><code>.Get()</code></li> <li><code>.GetOrTimeout()</code></li> <li><code>.GetChan()</code></li> </ul> </li> <li> <p>Set timeout for future</p> <ul> <li><code>.SetTimeout(ms)</code></li> </ul> </li> <li> <p>Merge multiple promises</p> <ul> <li><code>WhenAll(func1, func2, func3, ...)</code></li> <li><code>WhenAny(func1, func2, func3, ...)</code></li> <li><code>WhenAnyMatched(func1, func2, func3, ...)</code></li> </ul> </li> <li> <p>Pipe</p> <ul> <li><code>.Pipe(funcWithDone, funcWithFail)</code></li> </ul> </li> <li> <p>Cancel the future</p> <ul> <li><code>.Cancel()</code></li> <li><code>.IsCancelled()</code></li> </ul> </li> <li> <p>Create future by function</p> <ul> <li><code>Start(func() (r interface{}, e error))</code></li> <li><code>Start(func())</code></li> <li><code>Start(func(canceller Canceller) (r interface{}, e error))</code></li> <li><code>Start(func(canceller Canceller))</code></li> </ul> </li> <li> <p>Immediate wrappers</p> <ul> <li><code>Wrap(interface{})</code></li> </ul> </li> <li> <p>Chain API</p> <ul> <li><code>Start(taskDone).Done(done1).Fail(fail1).Always(alwaysForDone1).Pipe(f1, f2).Done(done2)</code></li> </ul> </li> </ul> <p>使用例子可以看他的项目文档。</p> <h2>参考资料</h2> <ol> <li><a href="/misc/goto?guid=4958964046395595771" rel="external">https://en.wikipedia.org/wiki/Futures_and_promises</a></li> <li><a href="/misc/goto?guid=4959670728846430417" rel="external">http://labs.strava.com/blog/futures-in-golang/</a></li> <li><a href="/misc/goto?guid=4959670728730954852" rel="external">https://github.com/fanliao/go-promise</a></li> </ol> <p>来源:http://colobu.com/2016/04/14/Futures-and-Promises-in-golang/ </p>