从 JavaScript 调用函数。

3

我正在尝试理解Go语言中的WebAssembly(wasm),因此编写了以下内容:

  1. 操作DOM
  2. 调用JS函数
  3. 定义可由JS调用的函数

前两个步骤都没问题,但是最后一个步骤却无法正常工作。当我运行代码时,出现了JavaScript错误function undefined。我的代码如下,问题出在sub函数。

package main

import (
    "syscall/js"
)

// func sub(a, b float64) float64

func sub(this js.Value, inputs []js.Value) interface{} {
    return inputs[0].Float() - inputs[1].Float()
}

func main() {
    c := make(chan int) // channel to keep the wasm running, it is not a library as in rust/c/c++, so we need to keep the binary running
    js.Global().Set("sub", js.FuncOf(sub))
    alert := js.Global().Get("alert")
    alert.Invoke("Hi")
    println("Hello wasm")

    num := js.Global().Call("add", 3, 4)
    println(num.Int())

    document := js.Global().Get("document")
    h1 := document.Call("createElement", "h1")
    h1.Set("innerText", "This is H1")
    document.Get("body").Call("appendChild", h1)

    <-c // pause the execution so that the resources we create for JS keep available
}

编译为 wasm 如下:

GOOS=js GOARCH=wasm go build -o main.wasm wasm.go

wasm_exec.js文件复制到与以下工作文件夹相同的位置:

cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .

我的HTML文件是:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>WASM</title>
    <script src="http://localhost:8080/www/lib.js"></script>
    <!-- WASM -->
    <script src="http://localhost:8080/www/wasm_exec.js"></script>
    <script src="http://localhost:8080/www/loadWasm.js"></script>
</head>
<body>
</body>
<script>
   console.log(sub(5,3));
</script>
</html>

lib.js 是:

function add(a, b){
    return a + b;
}

loadWasm.js 是:

async function init(){
    const go = new Go();
    const result = await WebAssembly.instantiateStreaming(
        fetch("http://localhost:8080/www/main.wasm"),
        go.importObject
    );
    go.run(result.instance);
}
init();

服务器代码是:

package main

import (
    "fmt"
    "html/template"
    "net/http"
)

func wasmHandler() http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        tmpl := template.Must(template.ParseFiles("www/home.html"))

        w.Header().Set("Content-Type", "text/html; charset=utf-8")
        w.Header().Set("Access-Control-Allow-Origin", "*")
        err := tmpl.Execute(w, nil)
        if err != nil {
            fmt.Println(err)
        }
    })
}

func main() {
    fs := http.StripPrefix("/www/", http.FileServer(http.Dir("./www")))
    http.Handle("/www/", fs)

    http.Handle("/home", wasmHandler())
    http.ListenAndServe(":8080", nil)

}

我得到的输出是:

enter image description here

更新

我尝试使用以下的TinyGO示例,但遇到了几乎相同的问题:

//wasm.go

package main

// This calls a JS function from Go.
func main() {
    println("adding two numbers:", add(2, 3)) // expecting 5
}

// module from JavaScript.
func add(x, y int) int

//export multiply
func multiply(x, y int) int {
    return x * y
}

编译后的结果为:

tinygo build -o main2.wasm -target wasm -no-debug
cp "$(tinygo env TINYGOROOT)/targets/wasm_exec.js" .

并且 server.go 如下:

package main

import (
    "log"
    "net/http"
    "strings"
)

const dir = "./www"

func main() {
    fs := http.FileServer(http.Dir(dir))
    log.Print("Serving " + dir + " on http://localhost:8080")
    http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
        resp.Header().Add("Cache-Control", "no-cache")
        if strings.HasSuffix(req.URL.Path, ".wasm") {
            resp.Header().Set("content-type", "application/wasm")
        }
        fs.ServeHTTP(resp, req)
    }))
}

而JS代码如下:

const go = new Go(); // Defined in wasm_exec.js

go.importObject.env = {
    'main.add': function(x, y) {
        return x + y
    }
    // ... other functions
}


const WASM_URL = 'main.wasm';

var wasm;

if ('instantiateStreaming' in WebAssembly) {
    WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
        wasm = obj.instance;
        go.run(wasm);
    })
} else {
    fetch(WASM_URL).then(resp =>
        resp.arrayBuffer()
    ).then(bytes =>
        WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
            wasm = obj.instance;
            go.run(wasm);
        })
    )
}

// Calling the multiply function:
console.log('multiplied two numbers:', exports.multiply(5, 3));

我得到的输出是: 输入图像描述

请参考以下链接:https://dev59.com/XMHqa4cB1Zd3GeqPrxK_#67983256。 - blackgreen
该函数在 wasm 主函数运行之后才可用。尝试使用嵌入在 body 中的脚本来启动 wasm,而不是使用异步函数。 - Burak Serdar
@blackgreen,请查看我的更新。 - Hasan A Yousef
@BurakSerdar,请查看我的更新。 - Hasan A Yousef
@HasanAYousef 我认为更新后的问题是您在调用 exports.multiply 之前没有等待设置承诺。如果您将对 multiply() 的调用移动到 go.run 调用之后的 .then 函数中,它可能会起作用。 - theMomax
1个回答

3
我找到了解决方法,我需要一些东西来检测和确认 wasm 是否已加载并准备好进行处理,就像JS中用于检查文档是否准备好一样:
if (document.readyState === 'complete') {
  // The page is fully loaded
}

// or

document.onreadystatechange = () => {
  if (document.readyState === 'complete') {
    // document ready
  }
};

因此,在我的代码中,wasm初始化函数是async,所以我在JS中使用了以下代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>WASM</title>
    <!-- WASM -->
    <script src="http://localhost:8080/www/wasm_exec.js"></script>
    <script src="http://localhost:8080/www/loadWasm.js"></script>
</head>
<body>
</body>
<script>
    (async () => {
        try {
            await init();
            alert("Wasm had been loaded")
            console.log(multiply(5, 3));
        } catch (e) {
            console.log(e);
        } 
    })(); 

/***** OR ****/
    (async () => {
        await init();
        alert("Wasm had been loaded")
        console.log(multiply(5, 3));
    })().catch(e => {
        console.log(e);
    });
/*************/
</script>
</html>

这帮助我确信文档已准备好进行处理并调用wasm函数。

wasm加载函数变得非常简单:

async function init(){
    const go = new Go();
    const result = await WebAssembly.instantiateStreaming(
        fetch("http://localhost:8080/www/main.wasm"),
        go.importObject
    );
    go.run(result.instance); 
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接