你好WebAssembly
概述
上篇介紹瞭如何成功執行了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
import (
"syscall/js"
)
func main() {
alert := js.Global().Get("alert")
alert.Invoke("Hello wasm!")
}
現在,當點選按鈕時,會彈出一條包含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() {
console.clear()
await go.run(ist)
inst = await WebAssembly.instantiate(mod, go.importObject)
它啟動wasm
二進位制檔案並等待它終止,然後重新例項化它以便下次執行。新增一個新的函式,它將接收並儲存Go
回撥,並在完成後立即解析Promise
:
let printMessage
let printMessageReceived
let resolvePrintMessageReceived
function setPrintMessage(callback) {
printMessage = callback
resolvePrintMessageReceived()
}
現在調整run()
函式以使用回撥:
async function run() {
console.clear()
printMessageReceived = new Promise(resolve => {
resolvePrintMessageReceived = resolve
})
const run = go.run(inst)
await printMessageReceived
printMessage('Hello Wasm!')
await run
inst = await WebAssembly.instantiate(mod, go.importObject)
現在Go
部分需要建立回撥,將其傳送給js
端並等待它被呼叫。需要一個channel
來通知回撥被呼叫了,然後編寫實際的printMessage()``func
:
var done = make(chan struct{})
func printMessage(args []js.Value) {
message := args[0].String()
fmt.Println(message)
done <- struct{}{}
}
正如所看到的,引數是在js.Value
的切片中接收到的,在第一個元素上呼叫js.Value.String()
轉化為Go
的string
來獲取message。現在可以在回撥中包裝這個func
,然後呼叫js
的setPrintMessage()
函式,就像呼叫window.alert()
時一樣,最後就是等待回撥被呼叫,這個很重要,因為回撥是在goroutine
中執行的,因此主goroutine
必須等待回撥被呼叫,否則wasm
二進位制會提前終止。
callback := js.NewCallback(printMessage)
defer callback.Close()
setPrintMessage := js.Global().Get("setPrintMessage")
setPrintMessage.Invoke(callback)
<-done
完整的Go
程式應如下所示:
import (
"fmt"
"syscall/js"
)
var done = make(chan struct{})
func main() {
callback := js.NewCallback(printMessage)
defer callback.Close()
setPrintMessage := js.Global().Get("setPrintMessage")
setPrintMessage.Invoke(callback)
<-done
}
func printMessage(args []js.Value) {
message := args[0].String()
fmt.Println(message)
done <- struct{}{}
}
編輯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
func printMessage(args []js.Value) {
message := args[0].String()
no++
fmt.Printf("Message no %d: %s\n", no, message)
}
建立回撥並將其傳送給js
端與我們前面的示例中完全相同,但是這一次沒有完成的channel
來通知什麼時候終止主goroutine
。一種方法是使用空select
無限制地阻塞主goroutine
。這不是很優雅,wasm
二進位制檔案永遠不會完全關閉,並且可能會在瀏覽器關閉wasm_exec.html
時被kill
。另一種方法就是監聽頁面事件來終止主goroutine
。
建立回撥來接收頁面的beforeunload
事件並通過一個channel
通知主goroutine
。這次新的beforeUnload()
函式將只接受一個js.Value
引數用來接受事件:
var beforeUnloadCh = make(chan struct{})
func beforeUnload(event js.Value) {
beforeUnloadCh <- struct{}{}
}
然後可以使用js.NewEventCallback()
將它包裝在一個回撥中,並將其註冊到js
端:
beforeUnloadCb := js.NewEventCallback(0, beforeUnload)
defer beforeUnloadCb.Close()
addEventListener := js.Global.Get("addEventListener")
addEventListener.Invoke("beforeunload", beforeUnloadCb)
最後用beforeUnloadCh
通道上的接收替換空select
:
<-beforeUnloadCh
fmt.Println("Bye Wasm !")
最終Go
程式如下所示:
package main
import (
"fmt"
"syscall/js"
)
var (
no int
beforeUnloadCh = make(chan struct{})
)
func main() {
callback := js.NewCallback(printMessage)
defer callback.Close() // This is a good practice
setPrintMessage := js.Global.Get("setPrintMessage")
setPrintMessage.Invoke(callback)
beforeUnloadCb := js.NewEventCallback(0, beforeUnload)
defer beforeUnloadCb.Close()
addEventListener := js.Global.Get("addEventListener")
addEventListener.Invoke("beforeunload", beforeUnloadCb)
<-beforeUnloadCh
fmt.Println("Bye Wasm !")
}
func printMessage(args []js.Value) {
message := args[0].String()
no++
fmt.Printf("Message no %d: %s\n", no, message)
}
func beforeUnload(event js.Value) {
beforeUnloadCh <- struct{}{}
}
現在在js
部分,這是wasm
二進位制檔案的載入:
const go = new Go();
let mod, inst;
WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then((result) => {
mod = result.module;
inst = result.instance;
document.getElementById("runButton").disabled = false;
});
修改讓它在載入後直接啟動wasm
二進位制檔案:
let run
(async function() {
const go = new Go()
const { instance } = await WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject)
run = go.run(instance)
})()
通過輸入框和按鈕來替換我們的Run
按鈕來觸發printMessage()
:
<input id="messageInput" type="text" value="Hello Wasm!">
<button
onClick="printMessage(document.querySelector('#messageInput').value)"
id="printMessageButton"
disabled
>
Print message
</button>
接收和儲存回撥的setPrintMessage()
函式變得簡單了:
let printMessage
function setPrintMessage(callback) {
printMessage = callback
document.querySelector('#printMessageButton').disabled = false
}
現在,當點選Print message
按鈕時,應該看到輸入的資訊和計數器輸出在console
中。然後,如果勾選瀏覽器控制檯的Preserve log
選項並重新整理頁面,則應該在console
中看到Bye Wasm !
。
最後
上面用簡單的例子和較少的程式碼測試了syscall/js
API,Go
與js
之間更容易的相互呼叫了。如果感興趣的可以做一些基準測試比較下Go
的wasm
與等效的純js
程式碼的效能。
原文連結: https://blog.keyboardman.me/2018/06/28/hello-webassembly/
相關文章
- webAssemblyWeb
- 你好
- 你好,promisePromise
- 你好laravelLaravel
- 你好,掘金
- LevelDB,你好~
- 你好,世界
- 你好,Spring!Spring
- 體驗WebAssemblyWeb
- WebAssembly元件提案Web元件
- 初識WebAssemblyWeb
- [譯][A crash course in WebAssembly] 創作並使用 WebAssembly 模組Web
- [譯][A crash course in WebAssembly] 為什麼WebAssembly這麼快Web
- [譯][A crash course in WebAssembly] WebAssembly的進度和計劃Web
- 你好,iLogtail 2.0!AI
- 你好,小友!
- 1、你好,PythonPython
- 再見、你好
- 你好,消防員
- WebAssembly 的由來Web
- [譯] WebAssembly: How and whyWeb
- 什麼是 webAssembly ?Web
- 你好,圖靈社群!圖靈
- 部落格園,你好!
- 20分鐘上手 webAssemblyWeb
- 【JSConf EU 2018】Rust + WebAssemblyJSRustWeb
- JavaScript也能寫WebAssemblyJavaScriptWeb
- [WebAssembly 入門] Hello, world!Web
- [譯][A crash course in WebAssembly] assemblyWeb
- WebAssembly學習筆記Web筆記
- 你好,我的部落格!
- 你好~ 我是小斑
- 你好,國慶作業。
- 你好,我是B樹
- 再見2020,你好2021!
- 再見2020,你好2021
- 再見,2020;你好,2021!
- WebAssembly入門,未來可期Web