golang 函数式编程库samber/mo使用: Future

golang 函数式编程库samber/mo使用: Future

如果您对samber/mo库不了解, 请先阅读第一篇 Option

本节讲述Future的使用,它可以帮助我们处理异步编程问题。

示例

我们先来看看下面代码的示例, 注释解释了每一步的操作。

go 复制代码
package main

import (
	"fmt"

	"github.com/samber/mo"
)

func main() {
	// resolve 在这里只是一个定义, NewFuture会以一个 goroutine 的方式执行 cb, 并且传递Future 的 resolve 和 reject
	value, err := mo.NewFuture(func(resolve func(string), reject func(error)) {
		// do something here
		if true { // 这里假定 do something 成功
			// 如果 do something 成功, 执行 resolve, 并传递一个值, 然后会执行 Then
			resolve("foobar")
		} else {
			// 告诉 do something 失败, 执行 reject, 并传递一个错误, 然后会执行 Catch
			reject(fmt.Errorf("failure"))
		}
	}).
		Then(func(s string) (string, error) {
			// 这里 s 就是 resolve 传递的值
			return s, nil
		}).
		Catch(func(err error) (string, error) {
			// 这里 err 就是 reject 传递的错误
			return "foobar", nil
		}).
		Finally(func(value string, err error) (string, error) {
			// 不管发生什么都会执行, value 是 resolve 传递的值, err 是 reject 传递的错误
			return value, nil
		}).
		Collect() // 等待 future 执行完毕, 并返回最终的值

	fmt.Println(value)
	fmt.Println(err)
	// Output:
	// foobar
	// <nil>
}

源码解析

根据mo.NewFuture的实现, 可以看出该函数做的事情就是构造一个Future, 然后执行activate函数, activate实际就是用 goroutine 执行cb函数, 并且将 Future 的 resolve 和 reject函数作为参数传递给cb。

go 复制代码
func NewFuture[T any](cb func(resolve func(T), reject func(error))) *Future[T] {
	future := Future[T]{
		cb:       cb,
		cancelCb: func() {},
		done:     make(chan struct{}),
	}

	future.active()

	return &future
}

func (f *Future[T]) active() {
	go f.cb(f.resolve, f.reject)
}

resolve的实现如下, 可以看到resolve做的事情就是用mo.OK包装value, 记录到result中,并且关闭f.done, 表明future已经完成。

resolve加锁的目的是为了确保后续Then或Finally不会同时进行。

go 复制代码
func (f *Future[T]) resolve(value T) {
	f.mu.Lock()
	defer f.mu.Unlock()

	f.result = Ok(value)
	if f.next != nil { 
	    // 这里如果不为空,表明next先于something注册,需要执行
		f.next.activeSync()
	}
	close(f.done)
}

我们来看看Then的实现,这个函数先对f执行加锁, 然后构造一个新的Future,这个新的Future的cb函数就是为了判断f的执行结果, 如果f的result不是error, 就执行Then注册的回调 cb。 所以如果f.cb函数执行resolve后返回, f.result.IsError()为false, 会执行Then中的回调。

最后的select表示如果f已完成,用goroutine 执行Then中的回调。如果f还没有完成,则留待f.cb的resolve或reject执行Then的回调。两种情况都会直接返回f.next,不会阻塞。这样就实现了Future的串联。

go 复制代码
func (f *Future[T]) Then(cb func(T) (T, error)) *Future[T] {
	f.mu.Lock()
	defer f.mu.Unlock()

	f.next = &Future[T]{
		cb: func(resolve func(T), reject func(error)) {
			if f.result.IsError() {
				reject(f.result.Error())
				return
			}
			newValue, err := cb(f.result.MustGet())
			if err != nil {
				reject(err)
				return
			}
			resolve(newValue)
		},
		cancelCb: func() {
			f.Cancel()
		},
		done: make(chan struct{}),
	}

	select {
	case <-f.done:
		f.next.active()
	default:
	}
	return f.next
}

Catch和Then的区别是如果f.result是error, 就执行Catch中的回调。Finally是不管f.result是什么, 都会执行Finally中的回调。

最后的Collect是用于等待Future执行完毕, 并返回最终的值。

go 复制代码
func (f *Future[T]) Collect() (T, error) {
	<-f.done
	return f.result.Get()
}

还有 Result 和 Either方法, 用于获取Future的执行结果, 会阻塞直到Future执行完毕(也就是先执行Collect)

相关推荐
Cc.Y2 小时前
Java零基础入门:字符串深度掌握——从基础API到StringBuilder性能优化
java·开发语言·性能优化
java资料站6 小时前
十一、评估测试
开发语言·人工智能·python
左手の明天6 小时前
SLIP 协议封装与解析详解:原理、C 代码与实战案例
linux·c语言·开发语言·c++
海上小飞龙7 小时前
【KMP算法-下篇】同一道题:Java 库函数 2600 微秒,手写 KMP 16 微秒
java·开发语言·算法
福大大架构师每日一题7 小时前
ollama v0.35.0发布:决策模型正式上线,接口直接返回选择、概率与评分
golang
朝朝辞暮i7 小时前
C++ 第 12 课:局部变量、作用域、变量生命周期
开发语言·c++·算法
狗凯之家源码网7 小时前
淘客口令落地页生成器 PHP 版深度评测
开发语言·php
画绛集美术7 小时前
本地化部署LLM用于美术史问答:一间美术教室的实践记录
开发语言·php
八解毒剂8 小时前
【计组】中央处理器CPU
java·开发语言·计算机组成原理
FakeOccupational8 小时前
【电路笔记 信号】DBPSK 波形查找表+脉冲成形(升余弦+根升余弦滤波)
开发语言·笔记