不能——recover仅对当前goroutine中由Go原生panic触发、且在同层defer中调用的场景有效;动态脚本引擎(如goja)自身异常不触发Go panic,仅当绑定的Go函数内主动panic时才可被捕获,且必须在该函数内部defer中调用recover。

recover 能捕获动态脚本引擎里的 panic 吗?
不能直接捕获——recover 只对**当前 goroutine 中由 panic 触发的、且在同层 defer 中调用的 recover 有效**。如果动态脚本(比如通过 goja、otto 或自研解释器)是在独立 goroutine 或非 Go 原生执行栈中 panic(例如 JavaScript 层抛出异常、或 C 代码 segfault),recover 完全无感知。
在 goja 中正确捕获脚本 panic 的标准做法
goja 自身不使用 Go 的 panic,而是用 goja.Exception 表示运行时错误;它会在 RunProgram、RunString 等方法里返回 error,而非触发 Go 层 panic。但如果你在 goja 的 Go 函数绑定里主动 panic(比如在 require 回调里写了 panic("boom")),那这个 panic 会逃逸到 Go 层——这时才需要 recover。
- ✅ 正确方式:对所有暴露给
goja的 Go 函数外层加defer+recover - ❌ 错误方式:只在
vm.RunString()外包一层recover—— 这捕不到函数内 panic - ⚠️ 注意:
goja的vm.Set()绑定函数时,必须手动包装,例如:vm.Set("fetch", func() interface{} { defer func() { if r := recover(); r != nil { // 记录并转为 goja.Error vm.Set("lastError", fmt.Sprintf("Go panic: %v", r)) } }() // 实际逻辑 return http.Get("https://example.com") })
为什么在 goroutine 里执行脚本时 recover 失效?
常见模式是用 go vm.RunString(script) 异步执行,然后期望外层 recover 捕获——这必然失败,因为 panic 发生在新 goroutine,而 recover 只作用于当前 goroutine。
- ✅ 解决方案:把
recover放进 goroutine 内部,配合 channel 传递错误 - ✅ 更稳妥做法:不用 goroutine,改用带超时的同步执行(
context.WithTimeout+vm.RunProgram) - ⚠️ 风险点:若脚本调用的 Go 函数启动了更多 goroutine(如
time.AfterFunc),那些 goroutine 的 panic 仍无法被主流程 recover
自研脚本引擎里 panic 逃逸的真实路径
如果你用 plugin、CGO 或 WASM(如 wazero)加载脚本,panic 的行为完全不同:
立即学习“go语言免费学习笔记(深入)”;
-
plugin:跨插件边界的 panic 会直接终止进程,recover无效 - CGO:C 层崩溃(SIGSEGV)不会触发 Go 的
panic,需用signal.Notify捕获SIGBUS/SIGSEGV,再调runtime.Goexit() -
wazero:WASM panic 是 runtime error,通过func.ModuleBuilder.Build()返回的error暴露,不是 Go panic
真正要靠 recover 的,只有“你在 Go 函数绑定里写的、且没做防护的代码”——这部分最容易被忽略,也最常导致服务崩溃。


















