用 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