I tried
js.Global().Call("throw", "yeet")
but got back
panic: 1:1: expected operand, found 'type' [recovered] wasm_exec.f7bab17184626fa7f3ebe6c157d4026825842d39bfae444ef945e60ec7d3b0f1.js:51 panic: syscall/js: Value.Call: property throw is not a function, got undefined
I see there's an Error
type defined in syscall/js
, but there's nothing about throwing it https://golang.org/pkg/syscall/js/#Error
It's not possible to throw
a JS error from WebAssembly. In JS, when you throw
a value, the JS runtime unwinds the stack to the nearest try
block, or logs an uncaught error. WASM execution is performed within an isolated sandbox in a separate execution environment that cannot directly access the JS stack. From the WASM docs:
Each WebAssembly module executes within a sandboxed environment separated from the host runtime using fault isolation techniques.
If WASM calls into JS code that throws, the error will be caught by the WASM runtime and handled as though the WASM code had panicked. WASM has access to traps, but those are intended to halt execution immediately at the runtime level (and aren't implemented in Go's syscall/js
module).
The idiomatic approach to representing code execution that may fail is to return a Promise
, then either resolve
that promise on success or reject
it on failure. The calling JS code can await the promise execution within a try/catch
block and handle the error there, or use promise chaining and handle errors in a .catch()
callback. Here's a brief example:
func main() {
c := make(chan struct{})
js.Global().Set("doSomething", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
resolve := args[0]
reject := args[1]
go func() {
data, err := doSomeWork()
if err != nil {
// err should be an instance of `error`, eg `errors.New("some error")`
errorConstructor := js.Global().Get("Error")
errorObject := errorConstructor.New(err.Error())
reject.Invoke(errorObject)
} else {
resolve.Invoke(js.ValueOf(data))
}
}()
return nil
})
promiseConstructor := js.Global().Get("Promise")
return promiseConstructor.New(handler)
})
<-c
}
Then, in your JS code:
(async () => {
try {
await window.doSomething();
} catch (err) {
console.log('caught error from WASM:', err);
}
}();
or
window.doSomething()
.then(_ => /* ... */)
.catch(err => {
console.log('caught error from WASM:', err);
});