go 中无原生promise,但可用带缓冲chan封装结果、goroutine启动异步任务,并结合select实现等效链式异步处理。

Go 里没有 Promise,但可以用 chan + goroutine 模拟出等效行为
Go 原生不提供 Promise/A+ 那类链式异步抽象,强行套用 JS 思维写 then/catch 会绕弯子。真正轻量、符合 Go 风格的做法是:用带缓冲的 chan 封装结果,配合 goroutine 启动异步任务,再用 select 或 等待 —— 本质是“异步任务 → 结果通道”的直译。
常见错误现象:
• 直接返回未启动的 goroutine,忘了用 go func() { ... }()
• 用无缓冲 chan 却没配 select 或超时,导致调用方永久阻塞
• 忘记关闭 chan 或忽略关闭时机,引发 goroutine 泄漏
- 推荐始终使用带缓冲的
chan struct{val T; err error},避免生产者/消费者节奏不匹配 - 如果要支持取消,传入
context.Context,并在goroutine内监听ctx.Done() - 不要试图封装
then方法——Go 的defer、if err != nil和select已足够清晰
怎么写一个可 await 的异步函数(类似 Promise.resolve().then(...))
核心是把“启动异步操作”和“等待结果”拆开:前者返回通道,后者用接收操作实现“await”。不需要泛型也能做到干净封装。
示例:模拟 HTTP 请求并解析 JSON
func fetchJSON(ctx context.Context, url string)
- 返回类型是只读通道
,明确语义:调用方只能收,不能发 - 缓冲大小为 1,确保无论成功失败都一定能写入,不会卡在
ch - 必须
defer close(ch),否则接收方无法通过range或判断ch关闭来感知完成 - 别在函数内直接
—— 那就变同步了,失去“可 await”的意义
如何处理错误和超时(替代 Promise.race([p1, p2]))
Go 里最自然的竞态控制是 select + context.WithTimeout,不是靠多个 channel 手动 race。错误不是额外分支,而是结果结构体的一部分。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
使用场景:调用第三方 API,要求 3 秒内返回,超时或解析失败都算错误
- 用
context.WithTimeout(ctx, 3*time.Second)包裹原始 ctx,传给异步函数 - 接收时用
select等待ch或ctx.Done(),避免死等 - 不要单独开一个 goroutine 去
time.Sleep后 close channel —— 这是反模式,context 更可靠 - 错误统一走
struct{...; err error}字段,接收方用if res.err != nil判断,不额外设 error channel
为什么不该用泛型封装 Then / Catch 方法
有人用泛型写 func (p Promise[T]) Then(f func(T) U) Promise[U],看似优雅,实际引入三重复杂度:类型推导混乱、channel 生命周期难管理、错误传播路径模糊。
典型问题:
• Then 返回的新 Promise 何时启动?是立即 go,还是等前一个完成后再 go?不同实现语义不一致
• 中间某个 Then panic,整个链路 recover 不到
• 调试时堆栈丢失,goroutine ID 难追踪
- Go 的并发模型鼓励“扁平化编排”,而不是嵌套回调。一个业务逻辑,用 2–3 个独立的
go fn()+ 若干select更易读 - 如果真需要复用逻辑,写普通函数,参数接收
,返回 <code>,不绑定任何 Promise 类型 - 标准库的
sync/errgroup和golang.org/x/sync/semaphore已覆盖绝大多数并发协调需求,比手写 Promise 链更稳
真正容易被忽略的是 channel 的所有权转移 —— 谁创建、谁关闭、谁接收,边界必须清晰。哪怕只多一个 goroutine 忘记 close,泄漏就发生了,而且不会报错,只会慢慢吃光内存。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










