如何在 Golang 的 wasm 中等待一个 JavaScript 的异步函数?

huangapple go评论75阅读模式
英文:

How to wait a js async function from golang wasm?

问题

我已经写了一个小函数await,用于处理来自Go的异步JavaScript函数:

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)
	// 我也尝试过 go func() {awaitable.Call("then", onResolve, onReject).Call("catch", onCatch)}()
	glg.Info("after await")
	<-done
	glg.Info("I never reach the end")
	return
}

问题是,无论我是否使用goroutine调用该函数,事件处理程序似乎都被阻塞,我被迫重新加载页面。我从未进入任何回调,我的通道也从未关闭。在Go中以wasm的方式调用Promise是否有任何惯用方法?

英文:

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(&quot;then&quot;).Type() != js.TypeFunction {
		return awaitable, true
	}
	done := make(chan struct{})


	onResolve := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		glg.Info(&quot;resolve&quot;)
		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(&quot;reject&quot;)
		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(&quot;catch&quot;)
		ret = args[0]
		ok = false
		close(done)
		return nil
	})
	defer onCatch.Release()


	glg.Info(&quot;before await&quot;)
	awaitable.Call(&quot;then&quot;, onResolve, onReject).Call(&quot;catch&quot;, onCatch)
    // i also tried go func() {awaitable.Call(&quot;then&quot;, onResolve, onReject).Call(&quot;catch&quot;, onCatch)}()
	glg.Info(&quot;after await&quot;)
	&lt;-done
	glg.Info(&quot;I never reach the end&quot;)
	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 ?

答案1

得分: 5

你不需要使用goroutines:D

我看到这段代码中有一些问题,这些问题使得代码不符合惯用法,并且可能导致一些错误(其中一些错误可能会锁定你的回调函数,并导致你所描述的情况):

  • 你不应该在处理函数内部关闭done通道。这可能会由于并发而导致意外关闭,而且这是一个不好的做法。
  • 由于执行顺序没有保证,改变外部变量可能会导致一些并发问题。最好只使用通道与外部函数进行通信。
  • onResolveonCatch的结果应该使用不同的通道。这可以更好地处理输出,并将结果分别排序给主函数,理想情况下通过select语句实现。
  • 没有必要使用单独的onRejectonCatch方法,因为它们重叠了彼此的职责。

如果我要设计这个await函数,我会简化它,变成这样:

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
	}
}

这样的话,函数就符合惯用法了,因为函数会返回resolve, reject数据,有点像Go中常见的result, err情况。而且处理不需要的并发会更容易,因为我们不会在不同的闭包中处理变量。

最后但并非最不重要的是,确保你在JavaScript的Promise中不要同时调用resolvereject,因为js.Func中的Release方法明确告诉你一旦释放了这些资源,就不应再访问它们:

// 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.
英文:

You don't need goroutines for that one 如何在 Golang 的 wasm 中等待一个 JavaScript 的异步函数?

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):

  • You shouldn't close the done channel inside the handling function. This can lead to unwanted closing due to concurrencies and it's a bad practice in general.
  • Since there's no guarantee in the order of execution, changing external variables might lead to some concurrency problems. It's best to communicate with the external functions using channels only.
  • The results from 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.
  • There's no need for separate 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 &lt;- 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 &lt;- args
		return nil
	})
	defer catchFunc.Release()

	awaitable.Call(&quot;then&quot;, thenFunc).Call(&quot;catch&quot;, catchFunc)

	select {
	case result := &lt;-then:
		return result, nil
	case err := &lt;-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.

huangapple
  • 本文由 发表于 2021年7月18日 14:45:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/68426700.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定