Go中异步任务的标准解法是goroutine+channel,而非模拟Future/Promise;应返回只读缓冲channel并配合context控制生命周期,避免手动管理关闭和错误传播。

Go 里没有 Future 或 Promise,别硬套 JS/Java 那套概念
Go 的并发模型压根不走回调链或状态机那一套。你写 future.Get() 或 promise.then() 这种代码,在 Go 里要么编译不过,要么是自己造的轮子——而且大概率掩盖了 goroutine 和 channel 的真实意图。
真正该问的是:「我需要异步启动一个任务,稍后取结果,同时不想阻塞主线程」。这在 Go 里对应的标准解法就是 goroutine + channel,不是模拟 Promise。
- 常见错误现象:
panic: send on closed channel或死锁,往往因为手动管理 channel 关闭时机出错 - 使用场景:HTTP 请求、数据库查询、文件读取等 I/O 密集型操作的非阻塞封装
- 性能影响:每个
goroutine开销约 2KB 栈空间,比 JS 的 microtask 贵,但比 Java 线程轻得多;channel 传递值会拷贝,大结构体记得传指针
用 chan T 模拟「结果可获取」的最简模式
把「获取异步结果」这件事,降维成「从 channel 里读一个值」。这是 Go 最自然、最不容易翻车的方式。
func asyncFetch() <-chan string {
ch := make(chan string, 1) // 缓冲 1,避免 goroutine 卡住
go func() {
result := "hello from background"
ch <- result // 写入即完成
close(ch) // 写完立刻关,告诉接收方:没更多了
}()
return ch
}调用方直接 等结果,或用 <code>select 加超时:
立即学习“go语言免费学习笔记(深入)”;
- 别用
chan 或 <code> 类型暴露发送端,只返回只读 channel(<code>)更安全 - 缓冲大小选 1 是关键:无缓冲 channel 要求收发双方同时就绪,容易导致 goroutine 永久阻塞
- 必须
close(ch),否则接收方无法判断「任务结束但没返回值」和「还在跑」的区别
带错误和取消的健壮版本:用 context.Context 控制生命周期
真实项目里,异步任务得能被取消、要处理错误、不能无限等待。这时候光靠 channel 不够,得拉上 context。
func asyncFetchWithContext(ctx context.Context) <-chan struct{ data string; err error } {
ch := make(chan struct{ data string; err error }, 1)
go func() {
select {
case <-ctx.Done():
ch <- struct{ data string; err error }{"", ctx.Err()}
return
default:
}
<pre class='brush:php;toolbar:false;'> // 模拟耗时操作
result, err := doSomething()
ch <- struct{ data string; err error }{result, err}
close(ch)
}()
return ch}
- 错误必须和数据一起传出来,Go 不支持「抛异常式」错误传播;别把
err单独塞另一个 channel,容易不同步 -
select里先检查ctx.Done(),再执行业务逻辑,否则取消信号可能被忽略 - 不要在 goroutine 里调用
ctx.WithCancel或ctx.WithTimeout—— 子 context 生命周期应由调用方控制
为什么不用第三方 promise 库?
确实有库如 github.com/agnivade/async 或手写的 Promise 类型,但它们带来的问题比解决的多:
- 额外抽象层遮蔽了 goroutine 调度行为,比如你没法用
runtime.Gosched()或观察 pprof 中的真实 goroutine 数量 - 链式调用(
.Then().Catch())会隐式创建多个 goroutine,难以追踪泄漏点 - 和标准库生态不兼容:比如
http.Client.Do返回*http.Response,不是某个Promise,强行包装反而增加心智负担
真要复用逻辑,用函数值 + channel 就够了,比如封装成 func(context.Context) (T, error) 类型,再丢给 goroutine 启动——清晰、可控、无隐藏成本。










