Golang中如何捕获调用"syscall/js"函数时的异常?

Golang中如何捕获调用"syscall/js"函数时的异常? 我正在使用 syscall/js 包,需要帮助找到一种方法来捕获调用 JavaScript 函数时抛出的异常。

以下是一个示例:

value := js.Global().Get("window").Get("localStorage")
...

如果你在浏览器中禁用了对本地存储的访问,那么它会引发以下异常:

image

并且无法恢复你的 wasm 代码。 感谢任何帮助。


更多关于Golang中如何捕获调用"syscall/js"函数时的异常?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

感谢 @skillian 不幸的是,它没有起作用。JS 调用异常并没有导致 Go 代码 panic,因此无法进行恢复。 Go 的 wasm 代码仅仅……在 .Get("localstorage") 这一行……停止了。

.Get("localstorage")

更多关于Golang中如何捕获调用"syscall/js"函数时的异常?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


syscall/js 的文档说明,当值不是 JavaScript 对象时,js.Value.Get 会引发恐慌,因此你应该能够“捕获异常”,就像在其他 Go 代码中从恐慌中恢复一样:

func tryGet(v js.Value, p string) (result js.Value, err error) {
    defer func() {
        if x := recover(); x != nil {
            var ok bool
            if err, ok = x.(error); !ok {
                err = fmt.Errorf("%v", x)
            }
        }
    }()
    return v.Get(p), nil
}

看起来你可能需要在JavaScript端捕获它,然后将其传回Go。

我不熟悉JavaScript,所以不知道这是否符合惯用法,但我写了:

function tryGet() {
	try {
		var source = arguments[0];

		for (var i = 1; i < arguments.length; i++)
			source = source[arguments[i]];

		return [source, null];
	}
	catch (err) {
		return [null, err];
	}
}

然后在我的Go代码中:

func tryGet(source js.Value, path ...string) (js.Value, error) {
	tryGetArgs := make([]any, len(path)+1)
	tryGetArgs[0] = source
	for i, v := range path {
		tryGetArgs[i+1] = js.ValueOf(v)
	}
	arr := js.Global().Call("tryGet", tryGetArgs...)
	if arr1 := arr.Index(1); !arr1.Equal(js.Null()) {
		return js.Value{}, &js.Error{arr1}
	}
	return arr.Index(0), nil
}

func main() {
	keepMainRunning := make(chan struct{})
	v, err := tryGet(js.Global(), "window", "localStorage")
	if err != nil {
		alert(err.Error())
		return
	}
	fmt.Println("result of tryGet:", v)
	<-keepMainRunning
}

在我的电脑上,访问 window.localStorage 没有抛出异常,但如果我使用无效的属性名,我会从 tryGet 的第二个返回值得到一个错误。

在Go WebAssembly中使用syscall/js调用JavaScript函数时,可以通过js.Global().Call("eval", ...)结合recover()来捕获异常。以下是具体的实现方法:

package main

import (
    "syscall/js"
)

// SafeCall 安全调用JavaScript函数,捕获异常
func SafeCall(fn js.Value, args ...interface{}) (result js.Value, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = r.(error)
        }
    }()
    
    result = fn.Invoke(args...)
    return result, nil
}

// 使用示例
func main() {
    // 获取localStorage
    localStorage := js.Global().Get("window").Get("localStorage")
    
    // 安全调用setItem
    _, err := SafeCall(localStorage.Get("setItem"), "key", "value")
    if err != nil {
        // 处理异常
        js.Global().Get("console").Call("log", "Error:", err.Error())
    }
    
    // 或者使用更通用的异常捕获包装器
    js.Global().Set("safeGetItem", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
        return js.Global().Call("eval", `
            try {
                return window.localStorage.getItem(` + args[0].String() + `);
            } catch(e) {
                return e.toString();
            }
        `)
    }))
}

对于更复杂的场景,可以创建一个通用的异常捕获包装器:

// TryCatch 使用JavaScript的try-catch包装函数调用
func TryCatch(fn func()) (exception js.Value) {
    js.Global().Set("__go_exception", js.Undefined())
    
    js.Global().Call("eval", `
        try {
            __go_fn();
        } catch(e) {
            __go_exception = e;
        }
    `)
    
    // 定义JavaScript端的回调函数
    js.Global().Set("__go_fn", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
        fn()
        return nil
    }))
    
    // 执行eval
    js.Global().Call("eval", `
        try {
            __go_fn();
        } catch(e) {
            __go_exception = e;
        }
    `)
    
    exception = js.Global().Get("__go_exception")
    if !exception.IsUndefined() {
        panic(js.Error{exception})
    }
    
    return exception
}

// 使用示例
func exampleUsage() {
    TryCatch(func() {
        // 可能抛出异常的代码
        js.Global().Get("window").Get("localStorage").Call("setItem", "test", "value")
    })
}

另外,也可以直接使用JavaScript的try-catch通过eval执行:

// ExecWithCatch 使用eval执行JavaScript代码并捕获异常
func ExecWithCatch(jsCode string) (result js.Value, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = r.(error)
        }
    }()
    
    wrappedCode := `
        try {
            ` + jsCode + `
        } catch(e) {
            throw e;
        }
    `
    
    result = js.Global().Call("eval", wrappedCode)
    return result, nil
}

// 使用示例
func main() {
    // 直接执行JavaScript代码并捕获异常
    _, err := ExecWithCatch(`window.localStorage.setItem("key", "value")`)
    if err != nil {
        js.Global().Get("console").Call("log", "Caught exception:", err.Error())
    }
}

这些方法通过在JavaScript层面使用try-catch来捕获异常,然后通过Go的recover()机制将异常传递回Go代码中处理。

回到顶部