在本教程中,我们将为我们的应用程序开发一个用户界面,处理错误,并从 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被格式化并打印在输出文本区。
你可以在浏览器中看到上述输出。我们已经成功地调用了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")
如果你还记得,jsonoutput 是index.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 作为输入。

程序崩溃了,堆栈痕迹如下。
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。你可以看到一个带有错误信息的警报。输出文本区也被清除了。

这样我们就到了本教程的结尾。
请留下您的意见和反馈。