Description
Proposal Details
Let's say I have an async JavaScript function:
globalThis.myAsyncFunction = async () => 42;
Ideally I would be able to do one of these:
var n int
n = js.Global().Call("myAsyncFunction").Wait().Int()
n = js.Global().Call("myAsyncFunction").Await().Int()
n = js.Await(js.Global().Call("myAsyncFunction")).Int()
n = (<-js.Global().Call("myAsyncFunction").Chan()).Int()
Creating a promise in Go code for use by JavaScript code is ok-ish (it's not great): just use the new Promise(callback)
constructor with a Go-defined js.Func
callback.
var jsDoThingCallback = js.FuncOf(func(this Value, args []Value) interface{} {
r1 := <-myChannelFromSomewhere
r2, err := someFuncThatUsesChans(r1)
if err != nil {
args[1].Invoke(errorConstructor.New(err.Error()))
return nil
}
r3, err := someFuncThatSleeps(r2)
if err != nil {
args[1].Invoke(errorConstructor.New(err.Error()))
return nil
}
args[0].Invoke(r3)
return nil
})
func DoThingAsync() js.Value {
return promiseConstructor.New(jsDoThingCallback)
}
The other direction -- unwrapping a JavaScript Promise
instance on the Go-side by waiting for it -- is worse.
// Ugh. I have to manage this intermediate channel myself and remember to handle some edge cases.
var n int
p := promiseConstructor.Call("resolve", js.Global().Call("myAsyncFunction"))
type result struct {
value js.Value
err error
}
ch := make(chan result, 1)
onFulfilled := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
ch <- result{args[0], nil}
close(ch)
return nil
})
defer onFulfilled.Release()
onRejected := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
ch <- result{js.Value{}, js.Error{args[0]}}
close(ch)
return nil
})
defer onRejected.Release()
p.Call("then", onFulfilled, onRejected)
r := <-ch
if r.err != nil {
panic(r.err)
}
n = r.value.Int()
// Would have to do *all that again* for another `await nextFunctionThatUsesResult(n)` 😭
Here's the algorithm for the ECMAScript 2025 Await( value )
abstract operation: https://tc39.es/ecma262/multipage/control-abstraction-objects.html#await
It seems like the .Wait()
method convention is already in the standard library with sync.WaitGroup
wg.Wait()
. Here's an idea for how that might look in Go code. I'm not a Go channels wizard so this might be the completely wrong way to do this.
// Wait waits for the thenable v to fulfill or reject and returns the resulting value or error.
// This is equivalent to the await operator in JavaScript.
func (v js.Value) Wait() (js.Value, error) {
p := promiseConstructor.Call("resolve", v)
type result struct {
value js.Value
err error
}
ch := make(chan result, 1)
onFulfilled := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
ch <- result{args[0], nil}
close(ch)
return nil
})
defer onFulfilled.Release()
onRejected := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
ch <- result{js.Value{}, js.Error{args[0]}}
close(ch)
return nil
})
defer onRejected.Release()
p.Call("then", onFulfilled, onRejected)
r := <-ch
// Unsure if want to panic on error, or return the error.
// If a synchronous function throws an error it panics. Don't know whether to stick
// to that convention or to return a (js.Value, error) multivalue.
return r.value, r.err
// OR
if r.err == nil {
return r.value
} else {
panic(r.err)
}
}
There's already some of this promise stuff in the Go standard library
go/src/net/http/roundtrip_js.go
Lines 129 to 245 in 1d0f5c4
.Wait()
or Await(v)
or something would be a good way to "one good way to do it"-ify this.Metadata
Metadata
Assignees
Type
Projects
Status