概述
上篇介绍了如何成功执行了Go
编译的第一个WebAssembly
(以下简称wasm
)二进制文件,接着进一步测试下Go
的wasm
的能实现的功能。
从Go调用JS
Go
的标准库有一个新的包syscall/js
,先看下js.go
文件。里面定义了个新的类型js.Value
,它表示一个JavaScript
值。它提供了一个简单的API来操纵任何类型的JavaScript
值并与之交互:
js.Value.Get()
和js.Value.Set()
检索并设置Object
值的属性js.Value.Index()
和js.Value.SetIndex()
检索并设置Array
值中的值js.Value.Call()
在一个Object
值上调用一个方法js.Value.Invoke()
调用一个函数值js.Value.New()
在代表JS
类型的引用上调用new
运算符- 在相应的
Go
类型中检索JavaScript
值的其他方法(如js.Value.Int()
或js.Value.Bool()
)
一个js.ValueOf()
函数,它接受任何Go
基本类型并返回相应的js.Value
。
最后是一些有趣的变量:
js.Undefined
与js
的undefined
对应的js.Value
js.Null
与js
的null
对应的js.Value
js.Global
允许访问js
全局范围的js.Value
尝试调用下js
的window.alert()
将消息其显示在对话框中,而不是发送到console
。
由于在浏览器中,global
就是window
,从global
中检索alert()
,于是有了一个alert
类型的js.Value
变量,它是对js
的window.alert
的引用,在其上使用js.Value.Invoke()
。可以发现在将参数传递给Invoke
之前不需要调用js.ValueOf()
,它接受interface{}
参数,并通过调用ValueOf
去运行。
package main |
现在,当点击按钮时,会弹出一条包含Hello wasm!
消息的对话框。
从JS调用Go
如上从Go
调用js
非常简单,接着看callback.go
文件。里面定义了一个新的js.Callback
类型,它代表一个Go
的func
包装以便用作js
回调。一个js.NewCallback()
函数,它接受一个js.Value
切片(并且不返回任何内容)并返回一个js.Callback
。并提供一些机制来管理活动回调,以及一个js.Callback.Close()
函数,当不再使用回调时必须调用它来释放相应资源。另外还有一个js.NewEventCallback()
函数来接受js事件。
先试着做一些简单的事情,从js
端触发Go
的fmt.Println
。
当前执行wasm
二进制文件的run()
函数如下所示,需要在wasm_exec.html
中进行一些调整,让它能够从Go
接收回调并调用它。
async function run() { |
它启动wasm
二进制文件并等待它终止,然后重新实例化它以便下次运行。添加一个新的函数,它将接收并存储Go
回调,并在完成后立即解析Promise
:
let printMessage |
现在调整run()
函数以使用回调:
async function run() { |
现在Go
部分需要创建回调,将其发送给js
端并等待它被调用。需要一个channel
来通知回调被调用了,然后编写实际的printMessage()``func
:
var done = make(chan struct{}) |
正如所看到的,参数是在js.Value
的切片中接收到的,在第一个元素上调用js.Value.String()
转化为Go
的string
来获取message。现在可以在回调中包装这个func
,然后调用js
的setPrintMessage()
函数,就像调用window.alert()
时一样,最后就是等待回调被调用,这个很重要,因为回调是在goroutine
中执行的,因此主goroutine
必须等待回调被调用,否则wasm
二进制会提前终止。
callback := js.NewCallback(printMessage) |
完整的Go
程序应如下所示:
import ( |
编辑wasm_exec.html
,继续重用wasm_exec.js
。现在,当点击按钮时,和之前的hello world
类似Hello Wasm!
消息被输出在console
中。
持续运行
从js
调用Go
比从Go
调用js
更麻烦一些,特别是在js
部分。这主要是因为需要等待Go
回调传递给js
,而且执行完就终止了,如何让wasm
不会在调用回调之后终止,却继续运行并接收其他调用?
这一次从Go
开始,同样需要创建一个回调并将它发送给js
端。并添加一个调用计数器,以便跟踪回调被调用的次数。新的printMessage()
函数将打印接收到的消息和调用计数器的值:
var no int |
创建回调并将其发送给js
端与我们前面的示例中完全相同,但是这一次没有完成的channel
来通知什么时候终止主goroutine
。一种方法是使用空select
无限制地阻塞主goroutine
。这不是很优雅,wasm
二进制文件永远不会完全关闭,并且可能会在浏览器关闭wasm_exec.html
时被kill
。另一种方法就是监听页面事件来终止主goroutine
。
创建回调来接收页面的beforeunload
事件并通过一个channel
通知主goroutine
。这次新的beforeUnload()
函数将只接受一个js.Value
参数用来接受事件:
var beforeUnloadCh = make(chan struct{}) |
然后可以使用js.NewEventCallback()
将它包装在一个回调中,并将其注册到js
端:
beforeUnloadCb := js.NewEventCallback(0, beforeUnload) |
最后用beforeUnloadCh
通道上的接收替换空select
:
<-beforeUnloadCh |
最终Go
程序如下所示:
package main |
现在在js
部分,这是wasm
二进制文件的加载:
const go = new Go(); |
修改让它在加载后直接启动wasm
二进制文件:
let run |
通过输入框和按钮来替换我们的Run
按钮来触发printMessage()
:
<input id="messageInput" type="text" value="Hello Wasm!"> |
接收和存储回调的setPrintMessage()
函数变得简单了:
let printMessage |
现在,当点击Print message
按钮时,应该看到输入的信息和计数器输出在console
中。然后,如果勾选浏览器控制台的Preserve log
选项并刷新页面,则应该在console
中看到Bye Wasm !
。
最后
上面用简单的例子和较少的代码测试了syscall/js
API,Go
与js
之间更容易的相互调用了。如果感兴趣的可以做一些基准测试比较下Go
的wasm
与等效的纯js
代码的性能。