用 Go 來開發 WebAssembly 入門(2)
原文:https://golangbot.com/go-webassembly-dom-access/
歡迎來到 WebAssembly 教程系列的第二篇。
在本教程系列的第一篇中,我們用 Go 創建並暴露了一個函數給 JavaScript 來調用。如果你還沒有看過第一篇,強烈建議先看一下再回來。
在本篇中,我們將爲我們的應用開發一個 UI,進行錯誤處理,以及從 Go 操縱瀏覽器的 DOM。
創建 UI 並調用 wasm 函數
我們先用 HTML 創建一個非常簡單的 UI。它包含一個獲取輸入 JSON 的文本區,一個提交按鈕用於對 JSON 進行格式化,以及另一個文本區用於顯示輸出。
我們對 assets 文件夾中的~/Documents/webassembly/assets/index.html 文件進行以下修改來實現這個 UI。
<html>
<head>
<meta charset="utf-8"/>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("json.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});
</script>
</head>
<body>
<textarea id="jsoninput" ></textarea>
<input id="button" type="submit" />
<textarea id="jsonoutput" ></textarea>
</body>
<script>
var json = function(input) {
jsonoutput.value = formatJSON(input)
}
</script>
</html>
以上 HTML 中的第 13 行,我們創建了一個 id 爲 jsoninput 的文本區。我們將在這裏輸入要進行格式化的 JSON。
接着,我們創建一個提交按鈕,當點擊該按鈕時,第 18 行的 json 函數將被調用。該函數以輸入的 JSON 作爲參數,調用我們在前一篇中創建的 formatJSON 函數,然後將輸出設置爲第 15 行所定義的 jsonoutput 文本區的 value 屬性。
我們來編譯並運行這段程序,看看它是否工作。
cd ~/Documents/webassembly/cmd/wasm/
GOOS=js GOARCH=wasm go build -o ../../assets/json.wasm
cd ~/Documents/webassembly/cmd/server/
go run main.go
打開瀏覽器,輸入 localhost:9090。你可以看到由兩個文本區和一個按鈕組成的 UI。
在第一個文本區輸入以下文本:
{"website":"golangbot.com", "tutorials": {"string":"https://golangbot.com/strings/", "maps":"https://golangbot.com/maps/", "goroutine":"https://golangbot.com/goroutines/", "channels":"https://golangbot.com/channels/"}}
然後點擊 pretty json 按鈕。你可以看到這段 JSON 被格式化並顯示在輸出文本區中。
你可以在瀏覽器中看到以上輸出。我們已經成功調用了 wasm 函數並格式化了輸入的 JSON。
從 Go 使用 JavaScript 訪問 DOM
在上一節,我們調用了 wasm 函數,獲得了 JSON 串的輸出,並用 JavaScript 將格式化的 JSON 串設置到輸出文本區。
還有一種方法可以得到相同的輸出。不需要將格式化好的 JSON 串傳遞到 JavaScript,而是從 Go 直接訪問瀏覽器的 DOM,將格式化好的 JSON 串設置到輸出文本區。
我們來看看怎麼做到這一點。
我們需要修改~/Documents/webassembly/cmd/wasm/main.go 中的 jsonWrapper 函數來實現它。
func jsonWrapper() js.Func {
jsonfunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 1 {
return "Invalid no of arguments passed"
}
jsDoc := js.Global().Get("document")
if !jsDoc.Truthy() {
return "Unable to get document object"
}
jsonOuputTextArea := jsDoc.Call("getElementById", "jsonoutput")
if !jsonOuputTextArea.Truthy() {
return "Unable to get output text area"
}
inputJSON := args[0].String()
fmt.Printf("input %s\n", inputJSON)
pretty, err := prettyJson(inputJSON)
if err != nil {
errStr := fmt.Sprintf("unable to parse JSON. Error %s occurred\n", err)
return errStr
}
jsonOuputTextArea.Set("value", pretty)
return nil
})
return jsonfunc
}
在第 6 行,我們試圖從 global 獲取 JavaScript 的 document 屬性。要訪問輸出文本區就必須先獲取該屬性。第 7 行的 Truthy 函數是 JavaScript 測試 nil 的方法。如果 Truthy 返回 false,意思是該屬性不存在。因此要返回一個相應的錯誤信息給 JavaScript。我們不能直接返回一個 Go error 類型。下一節將討論爲什麼會這樣以及如何處理錯誤。
在第 10 行,我們用 Call 方法來調用 jsDoc 對象上的 getElementById 函數,並傳入 jsonoutput 參數。在 JavaScript 中,這行代碼對應於:
jsDoc.getElementById("jsonoutput")
回想一下,jsonoutput 是 index.html 中的輸出文本區的 id。
這將返回 jsonoutput 文本區的引用。和之前一樣,我們用 Truthy 來驗證一下。
現在我們已可以訪問 jsonoutput 文本區了。在第 21 行,我們用 Set 方法將 jsonoutput 文本區的 value 屬性設置爲格式化後的 JSON 串。這樣格式化的 JSON 串將被顯示在輸出文本區。
Go 這一邊的程序修改就這麼多了。
我們還需要對~/Documents/webassemblu/assets/index.html 做少許改動。由於 JSON 串已由 Go 直接操縱瀏覽器的 DOM 來完成顯示了,就不需要在 JavaScript 中來做同樣的工作了,我們可以把相應代碼段刪掉。
把 index.html 中的第 19 行由
jsonoutput.value = formatJSON(input)
修改爲:
var result = formatJSON(input)
console.log("Value returned from Go", result)
我們從 JavaScript 中刪掉設置 jsonoutput 值的代碼,因爲這個工作已經在 Go 中完成了。我們只需將結果記錄在控制檯中。如果 JSON 輸入中有錯誤,則由 jsonfunc 返回的錯誤提示會被記錄在控制檯。
請注意,如果發生了錯誤,輸出文本區的內容並沒有被清除,還保留着原有的內容。下一節我們會修復這個錯誤。
使用以下命令再次運行該程序,然後在瀏覽器中打開 localhost:9090。
cd ~/Documents/webassembly/cmd/wasm/
GOOS=js GOARCH=wasm go build -o ../../assets/json.wasm
cd ~/Documents/webassembly/cmd/server/
go run main.go
輸出結果是一樣的。如果傳入的是一個合法 JSON,則會被格式化並打印出來。不過現在這個工作是由 Go 通過操縱 DOM 來完成的,而不是由 JavaScript 完成。如果你傳入一個非法 JSON,則相應的錯誤會記錄在控制檯中。
錯誤處理
在前一節中,在 JSON 格式化過程中,如果發生錯誤,我們只是從 jsonfunc 函數返回一個字符串。
在 Go 中處理錯誤的通常做法是返回一個 error。我們來試一下,把~/Documents/webassembly/cmd/wasm/main.go 中的 jsonWrapper 函數改爲返回一個 error,看看會發生什麼。
func jsonWrapper() js.Func {
jsonfunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 1 {
return errors.New("Invalid no of arguments passed")
}
jsDoc := js.Global().Get("document")
if !jsDoc.Truthy() {
return errors.New("Unable to get document object")
}
jsonOuputTextArea := jsDoc.Call("getElementById", "jsonoutput")
if !jsonOuputTextArea.Truthy() {
return errors.New("Unable to get output text area")
}
inputJSON := args[0].String()
fmt.Printf("input %s\n", inputJSON)
pretty, err := prettyJson(inputJSON)
if err != nil {
errStr := fmt.Sprintf("unable to parse JSON. Error %s occurred\n", err)
return errors.New(errStr)
}
jsonOuputTextArea.Set("value", pretty)
return nil
})
return jsonfunc
}
第 4 行被改爲了返回一個 error 而不是 string。其它幾個要返回 error 的地方也做了同樣的修改。
編譯,運行,然後輸入一個錯誤的 JSON,看看會發生什麼。下例中我輸入了一個非法的 JSON 串。
input dfs333{"website wasm_exec.js:47:14
panic: ValueOf: invalid value wasm_exec.js:47:14
<empty string> wasm_exec.js:47:14
goroutine 6 [running]: wasm_exec.js:47:14
syscall/js.ValueOf(0x1db00, 0x40e390, 0x6, 0x7ff8000100000017) wasm_exec.js:47:14
/usr/local/go/src/syscall/js/js.go:219 +0x13f wasm_exec.js:47:14
syscall/js.Value.Set(0x7ff8000100000012, 0x41a0d0, 0x3b31e, 0x6, 0x1db00, 0x40e390) wasm_exec.js:47:14
/usr/local/go/src/syscall/js/js.go:314 +0x7 wasm_exec.js:47:14
syscall/js.handleEvent() wasm_exec.js:47:14
/usr/local/go/src/syscall/js/func.go:91 +0x25 wasm_exec.js:47:14
exit code: 2 wasm_exec.js:138:14
Value returned from Go undefined
我們在上一篇教程中曾經提到過,jsonfunc 返回的任何值會通過 ValueOf 函數被自動映射爲相應的 JavaScript 值。如果你看一下這個函數的文檔,你會發現以對於 Go 的 error 類型來說,並沒有一個對應的 JavaScript 類型可以映射。這正是以上程序在 Go 返回一個 error 類型時發生了 "panic: ValueOf: invalid value" 錯誤並崩潰的原因。目前來說,沒有辦法從 Go 返回一個 error 給 JavaScript。以後可能會增加這一特性,但至少目前是不行的。我們必須看看有沒有其它方式來返回 error。
一種方法是,在 Go 和 JavaScript 間建立一個協議。例如,我們可以從 Go 返回一個 map 給 JavaScript。如果這個 map 帶有一個 error 的鍵,則可以被 JavaScript 視爲一個 error 並進行相應的處理。
我們來修改一下 jsonWrapper 函數。
func jsonWrapper() js.Func {
jsonfunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 1 {
result := map[string]interface{}{
"error": "Invalid no of arguments passed",
}
return result
}
jsDoc := js.Global().Get("document")
if !jsDoc.Truthy() {
result := map[string]interface{}{
"error": "Unable to get document object",
}
return result
}
jsonOuputTextArea := jsDoc.Call("getElementById", "jsonoutput")
if !jsonOuputTextArea.Truthy() {
result := map[string]interface{}{
"error": "Unable to get output text area",
}
return result
}
inputJSON := args[0].String()
fmt.Printf("input %s\n", inputJSON)
pretty, err := prettyJson(inputJSON)
if err != nil {
errStr := fmt.Sprintf("unable to parse JSON. Error %s occurred\n", err)
result := map[string]interface{}{
"error": errStr,
}
return result
}
jsonOuputTextArea.Set("value", pretty)
return nil
})
return jsonfunc
}
以上代碼的第 4 行,創建了一個名爲 result 的 map 並返回,它帶有一個 error 的鍵。在其它幾個地方也做了類似的改動。JavaScript 一側現在可以檢查 error 鍵是否存在。如果存在,這個返回值就表示發生了某種錯誤,可以進行相應的處理。
index.html 修改如下。僅需要修改第 17 行開始的一段 JavaScript 代碼。
...
<script>
var json = function(input) {
var result = formatJSON(input)
if (( result != null) && ('error' in result)) {
console.log("Go return value", result)
jsonoutput.value = ""
alert(result.error)
}
}
</script>
</html>
從 Go 返回的值首先檢查是否爲 null,然後檢查是否存在 error 鍵。如果存在,則表示在處理 JSON 時發生了某種錯誤。輸出文本區要被清空,然後彈出一個帶有錯誤信息的警告給用戶。
本教程到此爲止。
教程中的所有源代碼可以訪問 https://github.com/golangbot/webassembly/tree/tutorial2。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://blog.csdn.net/alai04/article/details/112286435?spm=1001.2014.3001.5501