err == Error 和 errors.Is(err, Error) 区别

结论

err == Error 直接比较错误对象与指定的错误对象,也就是指针的对比。

errors.Is(err, Error) 通过不断解包err,如果匹配错误类型,则返回 true。

例子解读

var BaseErr = errors.New("the underlying base error")
​
func main() {
   err1 := fmt.Errorf("wrap base: %w", BaseErr)
   err2 := fmt.Errorf("wrap err1: %w", err1)
   println(err2 == BaseErr) // false
   if !errors.Is(err2, BaseErr) {
      panic("err2 is not BaseErr")
   }
   println("err2 is BaseErr")
}
/*
  打印结果:
  false
  err2 is BaseErr
*/

这段代码演示了如何使用fmt.Errorf对错误进行包装,并使用errors.Is进行错误匹配。

  1. 通过fmt.Errorf创建了一个名为BaseErr的错误。
  2. BaseErr包装在err1中,再将err1包装在err2中。
  3. 通过比较err2BaseErr的相等性,发现它们不相等,因为err2经过了多次包装。
  4. 使用errors.Is判断err2是否包含BaseErr。由于err2包含了err1,而err1包含了BaseErr,所以err2也被认为包含了BaseErr
  5. 打印出结果"err2 is BaseErr",表示err2包含了BaseErr

这说明errors.Is确确实实通过解包err2来判断是否属于BaseErr类型

源码解读

func Is(err, target error) bool {
   if target == nil {
      return err == target
   }
​
   isComparable := reflectlite.TypeOf(target).Comparable()
   for {
      if isComparable && err == target {
         return true
      }
      if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
         return true
      }
      if err = Unwrap(err); err == nil {
         return false
      }
   }
}
​
func Unwrap(err error) error {
  u, ok := err.(interface {
    Unwrap() error
  })
  if !ok {
    return nil
  }
  return u.Unwrap()
}

在 Is 函数中,如果这个 err 自己实现了 interface{ Is(error) bool } 接口,通过接口断言,可以调用 Is 方法判断 err 是否与 target 相等,否则递归调用 Unwrap 方法拆包装,返回下一层的 error 去判断是否与 target 相等。

拓展 errors.As

提取指定类型的错误,判断包装的 error 链中,某一个 error 的类型是否与 target 相同,并提取第一个符合目标类型的错误的值,将其赋值给 target。

type TypicalErr struct {
   e string
}
​
func (t TypicalErr) Error() string {
   return t.e
}
​
func main() {
   err := TypicalErr{"typical error"}
   err1 := fmt.Errorf("wrap err: %w", err)
   err2 := fmt.Errorf("wrap err1: %w", err1)
   var e TypicalErr
   if !errors.As(err2, &e) {
      panic("TypicalErr is not on the chain of err2")
   }
   println("TypicalErr is on the chain of err2")
   println(err == e)
}
/*
  打印结果:
  TypicalErr is on the chain of err2
  true
*/

来看一下 error.As 方法的源码:

func As(err error, target any) bool {
   if target == nil {
      panic("errors: target cannot be nil")
   }
   val := reflectlite.ValueOf(target)
   typ := val.Type()
   if typ.Kind() != reflectlite.Ptr || val.IsNil() {
      panic("errors: target must be a non-nil pointer")
   }
   targetType := typ.Elem()
   if targetType.Kind() != reflectlite.Interface && !targetType.Implements(errorType) {
      panic("errors: *target must be interface or implement error")
   }
   for err != nil {
      if reflectlite.TypeOf(err).AssignableTo(targetType) {
         val.Elem().Set(reflectlite.ValueOf(err))
         return true
      }
      if x, ok := err.(interface{ As(any) bool }); ok && x.As(target) {
         return true
      }
      err = Unwrap(err)
   }
   return false
}

源码 for 循环前的部分是用来约束 target 参数的类型,要求其是一个非空的指针类型。此外要求 *target 是一个接口或者实现了 error 接口。

for 循环判断 err 是否可以赋值给 target 所属类型,如果可以则赋值返回 true。

如果 err 实现了自己的 As 方法,则调用其逻辑,否则也是走递归拆包的逻辑。

error 的定义

首先需要明确 Go 语言中的错误是通过接口定义的,因此是一个引用类型。

type error interface {
   Error() string
}
// Go 提供了一个默认实现
type errorString struct {
  s string
}
​
func (e *errorString) Error() string {
  return e.s
}

那么如果我要创建一个 error 实例,可以选择下面的方式:

func main() {
   // 此时创建的两个 error 都是 errorString 结构类型的
   errA := errors.New("new error a")
   fmt.Println(errA)
   errB := fmt.Errorf("new error %s", "b")
   fmt.Println(errB)
}
/*
  打印结果:
  new error a
  new error b
*/

wrapError 的定义

wrapError 是嵌套的 error ,也实现了 error 接口的 Error 方法,本质也是一个 error ,并声明了一个 Unwrap 方法用于拆包装。

type wrapError struct {
  msg string
  err error
}
​
func (e *wrapError) Error() string {
  return e.msg
}
​
func (e *wrapError) Unwrap() error {
  return e.err
}

通过 fmt.Errorf 方法配合 %w 占位符创建嵌套类型的 wrapError。

var BaseErr = errors.New("the underlying base error")
​
func main() {
  err1 := fmt.Errorf("wrap base: %w", BaseErr)
  fmt.Println(err1)
  err2 := fmt.Errorf("wrap err1: %w", err1)
  fmt.Println(err2)
}
/*
  打印结果:
  wrap base: the underlying base error
  wrap err1: wrap base: the underlying base error
*/

为什么 fmt.Errorf 用了占位符 %w 之后创建的就是 wrapError 类型,而用了 fmt.Errorf 但只是选择其他占位符如上述示例中的 %s 创建的就是 errorString 类型?

可以简单看一下 fmt.Errorf 方法的源码:

func Errorf(format string, a ...any) error {
   p := newPrinter()
   p.wrapErrs = true
   p.doPrintf(format, a)
   s := string(p.buf)
   var err error
   if p.wrappedErr == nil {
      err = errors.New(s)
   } else {
      err = &wrapError{s, p.wrappedErr}
   }
   p.free()
   return err
}

核心就是 p.doPrintf(format, a) 调用后,如果包含 %w 占位符则会先创建内层的 error ,赋值给 p.wrappedErr ,从而触发 wrapError 的创建逻辑。

你也可以进一步去看 p.doPrintf(format, a) 的实现印证这个流程。

errors.Is

判断被包装的error是否包含指定错误。

var BaseErr = errors.New("the underlying base error")
​
func main() {
   err1 := fmt.Errorf("wrap base: %w", BaseErr)
   err2 := fmt.Errorf("wrap err1: %w", err1)
   println(err2 == BaseErr) // false
   if !errors.Is(err2, BaseErr) {
      panic("err2 is not BaseErr")
   }
   println("err2 is BaseErr")
}
/*
  打印结果:
  false
  err2 is BaseErr
*/

来看一下 errors.Is 方法的源码:

func Is(err, target error) bool {
   if target == nil {
      return err == target
   }
​
   isComparable := reflectlite.TypeOf(target).Comparable()
   for {
      if isComparable && err == target {
         return true
      }
      if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
         return true
      }
      if err = Unwrap(err); err == nil {
         return false
      }
   }
}
​
func Unwrap(err error) error {
  u, ok := err.(interface {
    Unwrap() error
  })
  if !ok {
    return nil
  }
  return u.Unwrap()
}

如果这个 err 自己实现了 interface{ Is(error) bool } 接口,通过接口断言,可以调用 Is 方法判断 err 是否与 target 相等。

否则递归调用 Unwrap 方法拆包装,返回下一层的 error 去判断是否与 target 相等。

errors.As

提取指定类型的错误,判断包装的 error 链中,某一个 error 的类型是否与 target 相同,并提取第一个符合目标类型的错误的值,将其赋值给 target。

type TypicalErr struct {
   e string
}
​
func (t TypicalErr) Error() string {
   return t.e
}
​
func main() {
   err := TypicalErr{"typical error"}
   err1 := fmt.Errorf("wrap err: %w", err)
   err2 := fmt.Errorf("wrap err1: %w", err1)
   var e TypicalErr
   if !errors.As(err2, &e) {
      panic("TypicalErr is not on the chain of err2")
   }
   println("TypicalErr is on the chain of err2")
   println(err == e)
}
/*
  打印结果:
  TypicalErr is on the chain of err2
  true
*/

来看一下 error.As 方法的源码:

func As(err error, target any) bool {
   if target == nil {
      panic("errors: target cannot be nil")
   }
   val := reflectlite.ValueOf(target)
   typ := val.Type()
   if typ.Kind() != reflectlite.Ptr || val.IsNil() {
      panic("errors: target must be a non-nil pointer")
   }
   targetType := typ.Elem()
   if targetType.Kind() != reflectlite.Interface && !targetType.Implements(errorType) {
      panic("errors: *target must be interface or implement error")
   }
   for err != nil {
      if reflectlite.TypeOf(err).AssignableTo(targetType) {
         val.Elem().Set(reflectlite.ValueOf(err))
         return true
      }
      if x, ok := err.(interface{ As(any) bool }); ok && x.As(target) {
         return true
      }
      err = Unwrap(err)
   }
   return false
}

源码 for 循环前的部分是用来约束 target 参数的类型,要求其是一个非空的指针类型。

此外要求 *target 是一个接口或者实现了 error 接口。

for 循环判断 err 是否可以赋值给 target 所属类型,如果可以则赋值返回 true。

如果 err 实现了自己的 As 方法,则调用其逻辑,否则也是走递归拆包的逻辑。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在你给出的代码中,我注意到你在判断 `status` 是否为 `nil` 的时候并没有从 `status` channel 接收任何值。这可能导致主函数在判断之前无法正确等待 goroutine 的完成。我将为你提供一种修改后的方式来确保正确等待异步任务完成,并处理可能的错误。 ```go func PostHistoryAsync(a *decorator.ApiBase, data *HistorySync) error { logger.AccessLogger.Info("PostBusinessAgentAppUp...") var ( num int64 err error ) utils.Error(a.Ts.Table("history_" + data.PlatformCode + "_order"). Select("ctime"). Count(&num).Error) if num == data.EndTime-data.StartTime { return a.ReturnSuccessCustomResponse(a.NewSuccessResponseCommon()) } else { status := make(chan error, 1) // 修改为带有缓冲的 channel,容量设置为 1 go func() { err = GetOrderList(data.StartTime, data.EndTime, data.PlatformCode, data.ShopId, a.Ts) status <- err // 将错误值发送到 status channel }() err = <-status // 从 status channel 接收错误值 if err != nil { return errors.New("获取订单任务失败") } } return a.ReturnSuccessCustomResponse(a.NewSuccessResponseCommon()) } ``` 在这个修改后的代码中,我将 `status` channel 修改为带有缓冲的 channel,并将容量设置为 1。这样可以确保 goroutine 在发送错误值之前不会被阻塞。然后,我使用 `<-status` 语法从 channel 中接收错误值,并将其赋值给 `err` 变量。如果 `err` 不为 `nil`,则返回一个表示获取订单任务失败的错误信息。 这样的修改可以确保正确等待异步任务的完成,并处理可能的错误。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热爱技术的小胡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值