转载自:https://blog.csdn.net/a41888313/article/details/79691728,作了使信息更加清晰化的修订。
error 类型
官方文档介绍:
error 这一内建的接口类型,是表示错误状况(可为 nil)的常规接口。
panic 函数
官方文档介绍:
内建的 panic 函数停止当前 goroutine(协程)的正常执行。当函数 F 调用 panic 时,F 的正常执行立即停止。但是被 F 推迟的函数还都会执行,然后 F 返回到调用者。对于调用者 G 来说,F 的调用就像是调用了 panic 一样,终止 G 的执行并运行任何推迟(带有 defer 关键字)的函数。 这种情况会持续下去,直到正在执行的 goroutine 中所有的函数都以相反的顺序停止。 至此,程序终止,并报告错误情况(包括 panic 的参数值)。这种终止序列被称作 panicking,且可以通过内建的 recover 函数来控制。
recover 函数
官方文档介绍:
recover 内置函数允许一段程序管理一个正在 paincking 的 goroutine 的行为。在被 defer 的函数(而非由其调用的任何函数)内部执行 recover 函数,可以恢复正常执行并获取到传递给 panic 调用的错误值,以此来停止 panicking 序列函数的执行。 如果在被 defer 的函数之外调用 recover,则不会停止 panicking 序列的执行。在此情况下,或者是在 goroutine 没有 panicking 时,或者是提供给 panic 的参数为 nil 时,recover 返回 nil。也就是说,recover 函数的返回值报告了协程是否正在遭遇 panicking 。
panic 函数就是往外扔错误,一层接一层往上扔直到当前程序不能运行为止,如果不想让 panic 函数扔的错误导致程序挂掉,就得使用 recover 函数来接收 panic 错误或者说是阻挡 panicking。recover 函数可以将错误转化为 error 类型。panic 错误并不会让 defer 关键字声明的函数也停止运行,也就是说,defer 关键字声明的函数或者代码即使遇到错误也会执行。
一个函数里面有 defer 关键字声明一个函数(假设叫 catch 函数)和会运行出错的代码,在 catch 函数里面调用 recover 函数,recover 会拦截错误,不让错误往上扔,返回给调用者 error(里面有错误的信息)类型 ,从而使 goroutine 不挂掉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
package main import ( "fmt" "errors" ) func main() { testPanic() afterPanic() } func testPanic() { // defer catch() panic(" \"panic 错误\"") fmt.Println("testPanic(): panic() 后的代码") } func catch() { if r := recover(); r != nil { fmt.Println("catch(): recover() 捕获到:", r) var err error switch x := r.(type) { case string: err = errors.New(x) case error: err = x default: err = errors.New("") } if err != nil { fmt.Println("catch(): 转为 error 后:", err) } } } func afterPanic() { fmt.Println("afterPanic(): panic() 后的函数调用") } |
运行结果:
1 2 3 4 5 6 7 8 9 |
panic: "panic 错误" goroutine 1 [running]: main.testPanic() E:/goCode/src/MyTestGo/src/com.dylan.main/panic/testpanic.go:16 +0x40 main.main() E:/goCode/src/MyTestGo/src/com.dylan.main/panic/testpanic.go:10 +0x27 Process finished with exit code 2 |
当 panic 函数执行的时候导致后面函数 afterPanic() 不能执行,main 函数也抛出一个错误,整个程序异常退出。
取消代码中注释掉的 defer 关键字调用 catch 函数,程序运行结果:
1 2 3 4 |
catch(): recover() 捕获到:"panic 错误" catch(): 转为 error 后:"panic 错误" afterPanic(): panic() 后的函数调用 Process finished with exit code 0 |
分析:程序正常结束,没有因为 panic(错误)而到导致程序终止挂掉,且后面的 afterPanic() 也执行了。错误被 recover 函数捕获,转化为 error 类型的错误后输出“catch(): 转为 error 后:”panic 错误” ”。
一般情况下,不会采用上面这种显式定义 catch 函数的写法,而是在发生 panic 的函数里面写一个匿名的 defer 函数,就可以拦截 panicking, 并且不让程序挂掉和显示错误信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
func main() { testPanic() afterPanic() } func testPanic() { defer func() { if r := recover(); r != nil { fmt.Println("recover() 捕获到:", r) } }() panic(" \"panic 错误\"") fmt.Println("testPanic(): panic() 后的代码") } func afterPanic() { fmt.Println("afterPanic(): panic() 后的函数调用") } |
最后如果想将错误信息返回给调用者,可以改为如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
func main() { err := testPanic() if err != nil { fmt.Println("main(): testPanic() 返回错误:", err) } afterPanic() } func testPanic() (err error) { defer func() { if r := recover(); r != nil { fmt.Println("recover() 捕获到:", r) switch x := r.(type) { case string: err = errors.New(x) case error: err = x default: err = errors.New("") } } }() panic(" \"panic 错误\"") fmt.Println("testPanic(): panic() 后的代码") return nil } func afterPanic() { fmt.Println("afterPanic(): panic() 后的函数调用") } |
提前声明一个 error 类型的返回值变量,把错误信息转换到 error 变量,再将之返回给调用者即可。