用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" 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行,我们创建了一个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被格式化并显示在输出文本区中。

call Go function from JavaScript

你可以在浏览器中看到以上输出。我们已经成功调用了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串。

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返回一个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时发生了某种错误。输出文本区要被清空,然后弹出一个带有错误信息的警告给用户。

Go WebAssembly error handling

本教程到此为止。

教程中的所有源代码可以访问https://github.com/golangbot/webassembly/tree/tutorial2

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值