Go1-20 新特性:context 支持自定義取消原因
問題
熟悉 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
}
這些新特性有沒有給你編程帶來便利呢?歡迎留言 “開噴”!
我爲大家整理了一份從入門到進階的 Go 學習資料禮包,包含學習建議:入門看什麼,進階看什麼。關注公衆號 「polarisxu」,回覆 ebook 獲取;還可以回覆「進羣」,和數萬 Gopher 交流學習。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/HbFVODpDEzEZ047kv-LYbg