Repository: go
Issue: proposal: syscall/js: method or function to await a js.Value
Labels: Proposal

### Proposal Details

Let's say I have an async JavaScript function:

```js
globalThis.myAsyncFunction = async () => 42;
```

Ideally I would be able to do one of these:

```go
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.

```go
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.

```go
// 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.

```go
// 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 https://github.com/golang/go/blob/1d0f5c478ac176fa99d0f3d6bd540e5fb422187a/src/net/http/roundtrip_js.go#L129-L245 so it seems like this is a _thing that people need to do_. I think that providing a `.Wait()` or `Await(v)` or something would be a good way to "one good way to do it"-ify this.