Go1.20 新特性:context支持自定义错误1

问题

熟悉 Go 语言的同学都知道,context 包只对外提供了两种取消原因 context.DeadlineExceeded 和 context.Canceled,不支持自定义原因,就像下面这样:

func main() {
	// Pass a context with a timeout to tell a blocking function that it
	// should abandon its work after the timeout elapses.
	timeoutDuration := 3 * time.Second
	ctx, cancel := context.WithTimeout(context.Background(), timeoutDuration)
	defer cancel()

	// block until context is timed out
	<-ctx.Done()

	switch ctx.Err() {
	case context.DeadlineExceeded:
		fmt.Println("context timeout exceeded")
	case context.Canceled:
		fmt.Println("context cancelled by force")
	}

	// output:
	// context timeout exceeded
}

上面的两种错误已经能够满足大部分场景,但是如果有时候我想知道更多关于 context 取消的原因就只能额外自定义 error,比如遇到某类取消错误时是否需要重试等。

另外,如果是显示地调用 context.CancelFunc() 函数(上面代码的 cancel 变量)取消,现在是没有办法表明这是否是由于错误造成的。

介于此,之前社区就有人提案:如果是显示取消 context,允许自定义取消原因。

这不它就来了,Go1.20 目前已经支持这一特性。

自定义取消原因

Go1.20 的 context 包提供了 context.WithCancelCause() 支持自定义取消原因,并且提供了提取取消原因的 api:

package main

import (
	"context"
	"errors"
	"fmt"
)

var ErrTemporarilyUnavailable = fmt.Errorf("service temporarily unavailable")

func main() {
	ctx, cancel := context.WithCancelCause(context.Background())

	// operation failed, let's notify the caller by cancelling the context
	cancel(ErrTemporarilyUnavailable)

	switch ctx.Err() {
	case context.Canceled:
		fmt.Println("context cancelled by force")
	}

	// get the cause of cancellation, in this case the ErrTemporarilyUnavailable error
	err := context.Cause(ctx)

	if errors.Is(err, ErrTemporarilyUnavailable) {
		fmt.Printf("cancallation reason: %s", err)
	}

	// cancallation reason: service temporarily unavailable
}

上面的代码,在取消的时候传入了自定义错误 “ErrTemporarilyUnavailable”,并且使用 context.Cause() 提取错误原因。

有了这一特性,以后就可以基于取消原因,做更近一步的逻辑操作了,比如是否需要重试等。

更进一步

跟着 context.WithCancelCause() 之后,目前有最新的提案,支持 WithDeadlineCause 和 WithTimeoutCause,目前该提案已经被官方接受,正在开发。

我们先来尝鲜,看下这两个分别怎么用?

context.WithTimeoutCause()


var ErrFailure = fmt.Errorf("request took too long")

func main() {
	timeout := time.Duration(2 * time.Second)
	ctx, _ := context.WithTimeoutCause(context.Background(), timeout, ErrFailure)

	// wait for the context to timeout
	<-ctx.Done()

	switch ctx.Err() {
	case context.DeadlineExceeded:
		fmt.Printf("operation could not complete: %s", context.Cause(ctx))
	}

	// operation could not complete: request took too long
}

context.WithDeadlineCause()

var ErrFailure = fmt.Errorf("request took too long")

func main() {
	timeout := time.Now().Add(time.Duration(2 * time.Second))
	ctx, _ := context.WithDeadlineCause(context.Background(), timeout, ErrFailure)

	// wait for the context to timeout
	<-ctx.Done()

	switch ctx.Err() {
	case context.DeadlineExceeded:
		fmt.Printf("operation could not complete: %s", context.Cause(ctx))
	}

	// operation could not complete: request took too long
}

这些新特性有没有给你编程带来便利呢?欢迎留言“开喷”!

全部评论
暂无数据
Seekload11111111
公众号「Golang来啦」
  • 积分
    89
  • 话题
    7
  • 评论
    41
  • 注册排名
    8689