欢迎来到我们的 WebAssembly 系列教程的第 2 篇。

系列索引

Introduction to WebAssembly Using Go
Accessing DOM from Go using Javascript

在本系列教程的第一篇教程中,我们创建并暴露了一个 Go 的函数,并使用 JavaScript 调用它。如果你还没有阅读第一部分,我强烈建议你先看一下 https://golangbot.com/webassembly-using-go/

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

创建 UI 并调用 wasm 函数

让我们使用 HTML 创建一个非常简单的 UI。它将包含一个获取输入 JSON 的文本区域,一个用于格式化输入JSON 的提交按钮,以及另一个用于显示输出的文本区域。

让我们修改 assets 文件夹中现有的 ~/Documents/webassembly/assets/index.html 来包含 UI。

  1. <html>
  2. <head>
  3. <meta charset="utf-8"/>
  4. <script src="wasm_exec.js"></script>
  5. <script>
  6. const go = new Go();
  7. WebAssembly.instantiateStreaming(fetch("json.wasm"), go.importObject).then((result) => {
  8. go.run(result.instance);
  9. });
  10. </script>
  11. </head>
  12. <body>
  13. <textarea id="jsoninput" name="jsoninput" cols="80" rows="20"></textarea>
  14. <input id="button" type="submit" name="button" value="pretty json" onclick="json(jsoninput.value)"/>
  15. <textarea id="jsonoutput" name="jsonoutput" cols="80" rows="20"></textarea>
  16. </body>
  17. <script>
  18. var json = function(input) {
  19. jsonoutput.value = formatJSON(input)
  20. }
  21. </script>
  22. </html>

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

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

让我们来编译并运行这个程序,看看它是否有效。

  1. cd ~/Documents/webassembly/cmd/wasm/
  2. GOOS=js GOARCH=wasm go build -o ../../assets/json.wasm
  3. cd ~/Documents/webassembly/cmd/server/
  4. go run main.go

进入浏览器,输入 localhost:9090。你可以看到有两个文本区域和一个按钮的用户界面。

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

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

WebAssembly:DOM 访问和错误处理 - 图1

你可以在浏览器中看到上面的输出。我们已经成功地调用了 wasm 函数,并对 JSON 进行了格式化。

使用 JavaScript 从 Go 访问 DOM

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

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

让我们来看看是如何实现的。

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

  1. func jsonWrapper() js.Func {
  2. jsonfunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
  3. if len(args) != 1 {
  4. return "Invalid no of arguments passed"
  5. }
  6. jsDoc := js.Global().Get("document")
  7. if !jsDoc.Truthy() {
  8. return "Unable to get document object"
  9. }
  10. jsonOuputTextArea := jsDoc.Call("getElementById", "jsonoutput")
  11. if !jsonOuputTextArea.Truthy() {
  12. return "Unable to get output text area"
  13. }
  14. inputJSON := args[0].String()
  15. fmt.Printf("input %s\n", inputJSON)
  16. pretty, err := prettyJson(inputJSON)
  17. if err != nil {
  18. errStr := fmt.Sprintf("unable to parse JSON. Error %s occurred\n", err)
  19. return errStr
  20. }
  21. jsonOuputTextArea.Set("value", pretty)
  22. return nil
  23. })
  24. return jsonfunc
  25. }

在第 6 行,我们尝试从 global 范围内获取 JavaScript 的 document 属性。这个属性是访问输出 JSON 文本区域所需要的。第 7 行的 Truthy 函数是 JavaScript 测试 nil 的方法。如果 truthy 返回 false,则意味着该属性不存在。因此,相应的错误字符串会返回给 JavaScript。我们没有明确地返回 Go 错误类型。原因以及如何处理错误将在下一节介绍。

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

  1. 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 行

  1. jsonoutput.value = formatJSON(input)

改成

  1. var result = formatJSON(input)
  2. console.log("Value returned from Go", result)

我们已经删除了 JavaScript 中设置 jsonoutput 值的代码,因为这是从 Go 端完成的。我们只是将结果记录到控制台。如果 JSON 输入有错误,从 jsonfunc 返回的错误字符串将被记录到控制台。

请注意,如果出现错误,输出文本区域不会被清除。它仍然会继续显示其现有的内容。这将在下一节中得到解决。

尝试使用以下命令再次运行程序,然后在浏览器中打开 localhost:9090。

  1. cd ~/Documents/webassembly/cmd/wasm/
  2. GOOS=js GOARCH=wasm go build -o ../../assets/json.wasm
  3. cd ~/Documents/webassembly/cmd/server/
  4. go run main.go

输出将是一样的。如果传递的是一个有效的 JSON,它将被格式化并打印出来。现在是通过操作 DOM 从 Go 代码中完成,而不是从 JavaScript 中完成。如果你传递了一个无效的 JSON,相应的错误将被记录到控制台。

错误处理

在上一节中,当 JSON 格式化过程中发生错误时,我们只是从 jsonfunc 函数中返回一个字符串。

在 Go 中处理错误的惯用方法是返回错误。让我们修改 ~/Documents/webassembly/cmd/wasm/main.go 中的jsonWrapper 函数来返回一个错误,看看会发生什么。

  1. func jsonWrapper() js.Func {
  2. jsonfunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
  3. if len(args) != 1 {
  4. return errors.New("Invalid no of arguments passed")
  5. }
  6. jsDoc := js.Global().Get("document")
  7. if !jsDoc.Truthy() {
  8. return errors.New("Unable to get document object")
  9. }
  10. jsonOuputTextArea := jsDoc.Call("getElementById", "jsonoutput")
  11. if !jsonOuputTextArea.Truthy() {
  12. return errors.New("Unable to get output text area")
  13. }
  14. inputJSON := args[0].String()
  15. fmt.Printf("input %s\n", inputJSON)
  16. pretty, err := prettyJson(inputJSON)
  17. if err != nil {
  18. errStr := fmt.Sprintf("unable to parse JSON. Error %s occurred\n", err)
  19. return errors.New(errStr)
  20. }
  21. jsonOuputTextArea.Set("value", pretty)
  22. return nil
  23. })
  24. return jsonfunc
  25. }

第 4 行改为返回一个 error 而不是一个 string。在其他需要返回错误的地方也做了类似的改动。

编译并运行这段代码,尝试输入一个不正确的 JSON,看看会发生什么。我已经提供了无效的 JSON 字符串dfs333{“website 为输入。

WebAssembly:DOM 访问和错误处理 - 图2

程序已经崩溃,堆栈跟踪如下。

  1. input dfs333{"website wasm_exec.js:47:14
  2. panic: ValueOf: invalid value wasm_exec.js:47:14
  3. <empty string> wasm_exec.js:47:14
  4. goroutine 6 [running]: wasm_exec.js:47:14
  5. syscall/js.ValueOf(0x1db00, 0x40e390, 0x6, 0x7ff8000100000017) wasm_exec.js:47:14
  6. /usr/local/go/src/syscall/js/js.go:219 +0x13f wasm_exec.js:47:14
  7. syscall/js.Value.Set(0x7ff8000100000012, 0x41a0d0, 0x3b31e, 0x6, 0x1db00, 0x40e390) wasm_exec.js:47:14
  8. /usr/local/go/src/syscall/js/js.go:314 +0x7 wasm_exec.js:47:14
  9. syscall/js.handleEvent() wasm_exec.js:47:14
  10. /usr/local/go/src/syscall/js/func.go:91 +0x25 wasm_exec.js:47:14
  11. exit code: 2 wasm_exec.js:138:14
  12. Value returned from Go undefined

正如我们在上一篇教程中已经讨论过的,jsonfunc 返回的任何值都会通过 ValueOf 函数自动映射到相应的JavaScript 值。如果你快速看一下这个函数的文档,你可以看到 Go 的 error 类型没有映射到相应的 JavaScript 类型。这就是程序崩溃的原因:panic: ValueOf: invalid value 。当 error 类型从 Go 返回时,目前还没有办法将 Go 的错误传递给 Javascript。这个功能可以在未来添加,但目前还没有。我们必须在返回错误时考虑其他选项。

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

让我们修改 jsonWrapper 函数来实现这个目的。

  1. func jsonWrapper() js.Func {
  2. jsonfunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
  3. if len(args) != 1 {
  4. result := map[string]interface{}{
  5. "error": "Invalid no of arguments passed",
  6. }
  7. return result
  8. }
  9. jsDoc := js.Global().Get("document")
  10. if !jsDoc.Truthy() {
  11. result := map[string]interface{}{
  12. "error": "Unable to get document object",
  13. }
  14. return result
  15. }
  16. jsonOuputTextArea := jsDoc.Call("getElementById", "jsonoutput")
  17. if !jsonOuputTextArea.Truthy() {
  18. result := map[string]interface{}{
  19. "error": "Unable to get output text area",
  20. }
  21. return result
  22. }
  23. inputJSON := args[0].String()
  24. fmt.Printf("input %s\n", inputJSON)
  25. pretty, err := prettyJson(inputJSON)
  26. if err != nil {
  27. errStr := fmt.Sprintf("unable to parse JSON. Error %s occurred\n", err)
  28. result := map[string]interface{}{
  29. "error": errStr,
  30. }
  31. return result
  32. }
  33. jsonOuputTextArea.Set("value", pretty)
  34. return nil
  35. })
  36. return jsonfunc
  37. }

在上面的代码第 4 行中,创建了一个名为 result 的字典,该字典带有一个错误键,并以相应的错误返回。其他地方也做了类似的改动。现在 JavaScript 端可以检查这个键是否存在。如果这个键存在,说明发生了错误,可以进行适当的处理。

下面提供修改后的 index.html 文件。只对第 17 行开始的 JavaScript 部分进行了修改。

  1. ...
  2. <script>
  3. var json = function(input) {
  4. var result = formatJSON(input)
  5. if (( result != null) && ('error' in result)) {
  6. console.log("Go return value", result)
  7. jsonoutput.value = ""
  8. alert(result.error)
  9. }
  10. }
  11. </script>
  12. </html>

首先验证 Go 的返回值是否为空,然后检查是否存在 error 键。如果存在错误键,说明在处理 JSON 时发生了一些错误。首先清除输出文本区域,然后向用户弹出提示,并显示错误信息。

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

WebAssembly:DOM 访问和错误处理 - 图3

本教程到此结束。

源代码可以在 https://github.com/golangbot/webassembly/tree/tutorial2

请留下你的意见和反馈。

如果你想在这个网站上做广告,雇佣我,或者你有任何其他的开发需求,请发邮件到naveen[at]golangbot[dot]com。

原文链接

https://golangbot.com/go-webassembly-dom-access/