Go 中应避免模拟链式 Future,而用 select 配合多个 channel 实现“前一个异步结果就绪即启动下一个”的自然流程。

Go 里没有链式 Future,强行模拟 then 或 catch 只会增加复杂度、掩盖 goroutine 和 channel 的真实意图。你要的只是“上一个异步结果出来后,立刻启动下一个”,用 select + 多个 就能干净实现。
为什么不要写 func() *Future 封装
社区有些包(比如 gofuture)提供 .Then() 方法,但实际踩坑频繁:
-
panic: send on closed channel:多数因.Then()内部又启 goroutine,且对 channel 关闭时机判断错误 - 类型不安全:靠
interface{}传参,字段访问错误只能到运行时报,编译期拦不住 - 隐式调度:你调一次
.Then(),底层可能再起一个 goroutine,调度不可控,ctx.Done()也传不进去 - channel 被当成“可重复读取的对象”:第二次
读到零值,误判为有效结果
select 是 Go 链式等待的唯一正解
多个异步任务有依赖关系时,不用嵌套回调,也不用封装新类型,直接用 select 等待前一个结果,再发下一个:
- 前一个任务完成,
select立刻收到值,马上启动后续 goroutine - 所有 channel 必须是只读的(
),避免调用方误写入 - 每个 channel 缓冲大小设为 1,防止 goroutine 启动后卡在发送端
- 必须
defer close(ch),否则接收方无法区分“失败退出”和“还在跑”
func fetchUser(id int) <-chan struct{ name string; err error } {
ch := make(chan struct{ name string; err error }, 1)
go func() {
defer close(ch)
name, err := callUserAPI(id)
ch <- struct{ name string; err error }{name, err}
}()
return ch
}
<p>func fetchPosts(uid int) <-chan []string {
ch := make(chan []string, 1)
go func() {
defer close(ch)
posts, _ := callPostsAPI(uid)
ch <- posts
}()
return ch
}</p><p>// 链式调用:先取用户,再取其帖子
func getUserPosts(id int) <-chan []string {
out := make(chan []string, 1)
go func() {
defer close(out)
userCh := fetchUser(id)
select {
case user := <-userCh:
if user.err != nil {
return
}
postsCh := fetchPosts(user.name) // 注意:这里用 name 做参数,实际按需调整
select {
case posts := <-postsCh:
out <- posts
}
}
}()
return out
}错误必须随结果一起发出,不能丢
只用 chan int 无法区分“成功返回 0”和“出错没返回”,这是新手最常翻车的地方:
立即学习“go语言免费学习笔记(深入)”;
- 别用两个 channel 分开传值和错误(
chan T+chan error),调用方容易漏掉select的错误分支 - 推荐统一用
chan Result[T]:定义type Result[T any] struct { Value T; Err error } - 发送方无论成功失败都发一次,接收方一次接收、一次判断:
if r.Err != nil { … } else { use r.Value } - 千万别在 goroutine 里
panic:它不会冒泡到主流程,recover又难写难维护
多个任务并发+聚合,用 sync.WaitGroup 控制启动,用 select 收集结果
类似 Promise.all 的场景,不是靠“链式”,而是靠并发控制 + 统一等待:
- 启动 N 个 goroutine,每个往自己的带缓冲
chan Result[T]发一次结果 - 用
sync.WaitGroup确保全部启动完毕(不是等完成) - 主 goroutine 用
select循环接收 N 次,或配合context.WithTimeout设置总超时 - channel 方向性是编译期强制的:
表示“只能读”,<code>chan 表示“只能写”——写错方向,编译器立刻报错,这其实是 Go 在帮你守住边界
最容易被忽略的是 channel 的关闭时机和方向性。close 不是可选操作,它是接收方判断“任务已结束”的唯一信号;而只读/只写类型也不是注释,是编译器强制的契约。这两点错了,链式逻辑就从根上不可靠。


















