Go errors.Join 返回的错误怎么逐个用 errors.Is 判断
来源:17golang原创
时间:2026-09-09 06:06:42 495浏览 收藏
多个操作同时失败时,errors.Join 不会把错误变成只能按换行拆分的文本,而是返回一个包含多个子错误的错误树。判断其中是否有某个已知原因,直接对聚合结果调用 errors.Is 即可;只有需要把每个原因分别展示、统计或记录时,才读取 Unwrap() []error。
最小写法是errors.Is(joined, target)。它会检查聚合错误本身及其子错误,包括被%w包装的原因;不要解析Error()字符串,也不要用errors.Unwrap期待取出 Join 的切片。
errors.Join 为什么能被 errors.Is 命中
errors.Join 从 Go 1.20 开始提供多错误聚合能力。它会忽略传入的 nil,所有参数都是 nil 时返回 nil;非空结果实现的是 Unwrap() []error。标准库把这种结构视为错误树,errors.Is 会从当前节点开始检查,再按深度优先访问子节点。
package main
import (
"context"
"errors"
"fmt"
)
func main() {
errTimeout := context.DeadlineExceeded
errCache := errors.New("缓存不可用")
// 用 %w 保留缓存错误的可匹配关系,同时补充场景信息。
joined := errors.Join(errTimeout, fmt.Errorf("读取用户资料:%w", errCache))
// 业务分支按错误身份判断,不依赖 Error() 的显示文本。
fmt.Println(errors.Is(joined, context.DeadlineExceeded)) // true
fmt.Println(errors.Is(joined, errCache)) // true
}

这里两个判断都可能为 true,因为目标分别位于 Join 的不同分支。目标错误必须是可比较的;如果项目定义了自定义错误类型,也可以实现 Is(error) bool 来声明等价关系。判断成功只说明树中存在匹配项,并不表示所有操作都成功。
怎么对每个已知原因分别判断
“逐个判断”通常不是手写循环,而是对每个业务关心的目标各调用一次 errors.Is。例如批量刷新资料时,可以把超时、权限和缓存故障分别映射到不同处理策略:
func classify(err error) string {
// 先判断更需要单独处理的业务原因。
switch {
case errors.Is(err, context.DeadlineExceeded):
return "稍后重试"
case errors.Is(err, errPermission):
return "提示重新授权"
case errors.Is(err, errCache):
return "降级读取"
default:
return "记录未知错误"
}
}
多个目标同时存在时,switch 只会返回第一个命中的分支;如果要同时收集标签,就分别执行判断:
func labels(err error) []string {
labels := make([]string, 0, 3)
// 每个目标独立判断,Join 中命中几个就保留几个标签。
if errors.Is(err, context.DeadlineExceeded) {
labels = append(labels, "timeout")
}
if errors.Is(err, errPermission) {
labels = append(labels, "permission")
}
if errors.Is(err, errCache) {
labels = append(labels, "cache")
}
return labels
}
不要用 err.Error() == "缓存不可用" 或 strings.Contains 替代身份判断。错误文本可以随着上下文变化,errors.Is 才是包装和聚合关系的稳定入口。
需要逐个查看原因时怎么展开错误树
日志、指标或批量响应有时确实需要列出直接子错误。这时使用 Unwrap() []error 的接口断言。注意,errors.Unwrap(err) 只识别返回单个 error 的 Unwrap() error,对 errors.Join 会返回 nil。
type multiUnwrapper interface {
Unwrap() []error
}
func walk(err error, visit func(error)) {
if err == nil {
return
}
visit(err)
// 先展开多错误节点;没有切片时再展开普通 %w 包装。
if many, ok := err.(multiUnwrapper); ok {
for _, child := range many.Unwrap() {
walk(child, visit)
}
return
}
if one := errors.Unwrap(err); one != nil {
walk(one, visit)
}
}

遍历函数适合做日志明细或错误统计,不适合替代业务分支。遍历时要保留去重策略,否则同一个 sentinel 被多个包装层引用时,展示列表可能重复;而业务是否命中仍然交给 errors.Is。
生产环境最容易踩到的四个边界
- 全是 nil:
errors.Join(nil, nil)返回nil,调用方可以直接按无错误处理。 - 嵌套聚合:Join 的子节点还可以是另一个 Join,
errors.Is会继续检查嵌套树。 - 展示和判断分离:
Error()适合给人看,errors.Is适合程序判断;不要互相替代。 - 顺序影响策略:一次只返回一个处理结果时,按业务优先级排列判断;要完整反映状态就独立收集多个命中结果。
速查结论:判断某个原因用 errors.Is(joined, target);判断自定义错误类别用 errors.As;读取 Join 的直接子节点用 Unwrap() []error;不要通过字符串切分恢复错误关系。
相关问题
errors.Join 和 fmt.Errorf 多个 %w 有什么区别?
两者都能形成包含多个子错误的错误树,errors.Is 和 errors.As 都可以继续匹配;选择时看你是要聚合已有错误,还是要在一段上下文文本中同时包装多个参数。
为什么 errors.Unwrap(errors.Join(...)) 是 nil?
因为 errors.Unwrap 只调用 Unwrap() error,而 Join 实现的是 Unwrap() []error。需要读取子错误时应做接口断言,或直接使用 errors.Is 进行判断。
-
394 收藏
-
342 收藏
-
418 收藏
-
277 收藏
-
195 收藏
-
147 收藏
-
137 收藏
-
159 收藏
-
183 收藏
-
108 收藏
-
298 收藏
-
190 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习