WebAssembly教程|Go中DOM访问和错误处理

74 阅读7分钟

在本教程中,我们将为我们的应用程序开发一个用户界面,处理错误,并从 Go 中操作浏览器的 DOM。

创建用户界面和调用wasm函数

让我们用HTML创建一个非常简单的用户界面。它将包含一个文本区来获取输入的JSON,一个提交按钮来格式化输入的JSON,还有一个文本区来显示输出。

让我们修改assets 文件夹中现有的~/Documents/webassembly/assets/index.html ,以包括这个用户界面。

<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" name="jsoninput" cols="80" rows="20"></textarea>
         <input id="button" type="submit" name="button" value="pretty json" onclick="json(jsoninput.value)"/>
         <textarea id="jsonoutput" name="jsonoutput" cols="80" rows="20"></textarea>
    </body>
    <script>
        var json = function(input) {
            jsonoutput.value = formatJSON(input)
        }
     </script>
</html>  

在上述HTML的第13行中,我们创建了在上述HTML的第13行,我们创建了一个文本区,ID为jsoninput 。这将是我们的文本区,我们在这里输入要格式化的JSON。

接下来,我们创建一个提交按钮,当该按钮被点击时,第18行的json JavaScript函数将被调用。18行将被调用。这个函数将输入的JSON作为参数,调用我们在上一个教程中创建的formatJSON wasm函数,并将输出设置到第18行定义的jsonoutput 文本区。15.

让我们编译并运行这个程序,看看它是否工作。

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 。你可以看到用户界面有两个文本区和一个按钮。

在第一个文本区中输入以下文字。

{"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被格式化并打印在输出文本区。

call Go function from JavaScript 你可以在浏览器中看到上述输出。我们已经成功地调用了wasm函数并格式化了JSON。

使用JavaScript从Go访问DOM

在上节中,我们调用了wasm函数,得到了格式化的JSON字符串输出,并使用JavaScript将格式化的JSON设置在输出文本区。

还有一种方法可以实现同样的输出。与其将格式化的JSON字符串传递给javascript,不如从Go中访问浏览器的DOM,将格式化的JSON字符串设置到输出文本区。

让我们来看看这是如何做到的。

jsonWrapper 我们需要修改~/Documents/webassembly/cmd/wasm/main.go 中的函数来实现这一点。

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行。6,我们尝试从global 范围中获得JavaScript的document 属性。这个属性需要用来访问输出的JSON文本区。第7行的Truthy函数是JavaScript的测试方法。7是JavaScript测试nil 的方式。如果truthy返回false,这意味着该属性不存在。因此,适当的错误字符串会返回给JavaScript。我们没有明确地返回Go的错误类型。这方面的原因以及如何处理错误将在下一节介绍。

在第10行中,我们使用调用方法来处理错误。10,我们使用调用方法,在jsDoc 的JavaScript对象上调用getElementById 函数,并将jsonoutput 参数传递给它。在JavaScript中,这一行代码对应的是。

jsDoc.getElementById("jsonoutput")  

如果你还记得,jsonoutputindex.html 中输出文本区的id

这将返回对jsonoutput 文本区的引用。正如我们先前所做的,我们检查truthy

现在我们可以访问jsonoutput 文本区。在第21行,我们使用set方法将jsonoutput 文本区的value 属性设置为格式化的JSON字符串。这将在输出文本区显示格式化的JSON。

对程序的Go方面的修改已经完成。

~/Documents/webassembly/assets/index.html 中需要做一个小的改动。由于JSON是通过操作浏览器的DOM而不是JavaScript从Go中直接设置的,我们可以删除下面这段代码。

改变第19行。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中处理错误的习惯方法是返回错误。让我们修改~/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 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行被修改为返回一个在其他需要返回错误的地方也做了类似的修改,第4行被修改为返回error ,而不是string

编译并运行代码,尝试输入一个错误的JSON,看看会发生什么。我已经提供了无效的JSON字符串dfs333{"website 作为输入。

panic: ValueOf: invalid value wasm_exec.js

程序崩溃了,堆栈痕迹如下。

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传递给Javascript。这个功能可以在将来加入,但目前还没有。在返回错误时,我们必须研究其他选项。

一种方法是在Go和JavaScript之间建立一个契约。例如,我们可以从Go向JavaScript返回一个地图。如果该地图包含一个error ,就可以被JavaScript认为是一个错误,并进行适当的处理。

让我们修改一下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 的地图,其中有一个error 的键,并返回相应的错误。在其他地方也做了类似的修改。现在JavaScript端可以检查这个键是否存在。如果这个键存在,就意味着发生了错误,可以进行适当的处理。

下面提供了修改后的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时发生了一些错误。输出文本区首先被清除,然后向用户显示一个弹出式警报,其中包含错误信息。

再次编译并运行该程序。尝试传递一个无效的JSON。你可以看到一个带有错误信息的警报。输出文本区也被清除了。

Go WebAssembly error handling

这样我们就到了本教程的结尾。

源代码可在github.com/golangbot/w…

请留下您的意见和反馈。