I have written a little function await
in order to handle async javascript function from go:
func await(awaitable js.Value) (ret js.Value, ok bool) {
if awaitable.Type() != js.TypeObject || awaitable.Get("then").Type() != js.TypeFunction {
return awaitable, true
}
done := make(chan struct{})
onResolve := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
glg.Info("resolve")
ret = args[0]
ok = true
close(done)
return nil
})
defer onResolve.Release()
onReject := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
glg.Info("reject")
ret = args[0]
ok = false
close(done)
return nil
})
defer onReject.Release()
onCatch := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
glg.Info("catch")
ret = args[0]
ok = false
close(done)
return nil
})
defer onCatch.Release()
glg.Info("before await")
awaitable.Call("then", onResolve, onReject).Call("catch", onCatch)
// i also tried go func() {awaitable.Call("then", onResolve, onReject).Call("catch", onCatch)}()
glg.Info("after await")
<-done
glg.Info("I never reach the end")
return
}
The problem is that when I call the function with or without goroutine, the event handler seems blocked and I'm forced to reload the page. I never get into any callback and my channel is never closed. Is there any idiomatic way to call await on a promise from Golang in wasm ?
You don't need goroutines for that one :D
I see a few problems in this code that doesn't make it idiomatic and can lead to some errors (some of which might lock your callback and lead to this situation you're describing):
done
channel inside the handling function. This can lead to unwanted closing due to concurrencies and it's a bad practice in general.onResolve
and onCatch
should use separate channels. This can lead to better handling of the outputs and sort out the results for the main function individually, ideally through a select
statement.onReject
and onCatch
methods, since they are overlapping each other's responsibilities.If I had to design this await
function, I'd simplify it a bit to something like this:
func await(awaitable js.Value) ([]js.Value, []js.Value) {
then := make(chan []js.Value)
defer close(then)
thenFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
then <- args
return nil
})
defer thenFunc.Release()
catch := make(chan []js.Value)
defer close(catch)
catchFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
catch <- args
return nil
})
defer catchFunc.Release()
awaitable.Call("then", thenFunc).Call("catch", catchFunc)
select {
case result := <-then:
return result, nil
case err := <-catch:
return nil, err
}
}
This would make the function idiomatic, as the function would return resolve, reject
data, kinda like a result, err
situation that is common in Go. Also a bit easier to deal with unwanted concurrencies since we don't handle variables in different closures.
Last but not least, make sure you're not calling both resolve
and reject
in your Javascript Promise
, as the Release
method in js.Func
explicitly tells that you should not access such resources once they are released:
// Release frees up resources allocated for the function.
// The function must not be invoked after calling Release.
// It is allowed to call Release while the function is still running.