从JavaScript调用函数

huangapple go评论83阅读模式
英文:

Calling a function from JavaScript

问题

尝试理解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)

}

我得到的输出是:

从JavaScript调用函数

更新

我尝试使用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));

我得到的输出是:
从JavaScript调用函数

英文:

Trying to understand wasm in go, so I wrote the below that:

  1. Manipulate DOM
  2. Call JS function
  3. Define a function that can called by JS

first 2 steps are fine, but the last one is not working as expected, as I got the JavaScript error function undefined, my code is below, the issue I have is in the function sub

package main

import (
	&quot;syscall/js&quot;
)

// 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(&quot;sub&quot;, js.FuncOf(sub))
	alert := js.Global().Get(&quot;alert&quot;)
	alert.Invoke(&quot;Hi&quot;)
	println(&quot;Hello wasm&quot;)

	num := js.Global().Call(&quot;add&quot;, 3, 4)
	println(num.Int())

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

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

compiled it to wasm as:

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

Copied the wasm_exec.js file to the same working folder as:

cp &quot;$(go env GOROOT)/misc/wasm/wasm_exec.js&quot; .

My HTML file is:

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

The lib.js is:

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

The loadWasm.js is:

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

The server code is:

package main

import (
	&quot;fmt&quot;
	&quot;html/template&quot;
	&quot;net/http&quot;
)

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

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

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

	http.Handle(&quot;/home&quot;, wasmHandler())
	http.ListenAndServe(&quot;:8080&quot;, nil)

}

The output i got is:

从JavaScript调用函数

UPDATE

I tried using the TinyGO example as below, but got almost the same issue:

//wasm.go

package main

// This calls a JS function from Go.
func main() {
	println(&quot;adding two numbers:&quot;, 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
}

Compliled it as:

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

And server.go as:

package main

import (
	&quot;log&quot;
	&quot;net/http&quot;
	&quot;strings&quot;
)

const dir = &quot;./www&quot;

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

And the JS code as:

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

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


const WASM_URL = &#39;main.wasm&#39;;

var wasm;

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

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

And the output i got is:
从JavaScript调用函数

答案1

得分: 3

我找到了解决方案,我需要一个能够检测和确认wasm已加载并准备好处理的东西,就像在JS中检查文档是否准备好一样:

if (document.readyState === 'complete') {
  // 页面已完全加载
}

// 或者

document.onreadystatechange = () => {
  if (document.readyState === 'complete') {
    // 文档已准备好
  }
};

因此,由于我的代码中的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已加载");
            console.log(multiply(5, 3));
        } catch (e) {
            console.log(e);
        } 
    })(); 

/***** OR ****/
    (async () => {
        await init();
        alert("Wasm已加载");
        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); 
}
英文:

I found the solution, that I need something to detect and confirm that wasm had been loaded and ready for processing, same the one used in JS to check if the document is ready:

if (document.readyState === &#39;complete&#39;) {
  // The page is fully loaded
}

// or

document.onreadystatechange = () =&gt; {
  if (document.readyState === &#39;complete&#39;) {
    // document ready
  }
};

So, as wasm initiation function in my code is async I used the below in JS:

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

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

This helped me been sure that the document is ready to process and call the wasm function.

The wasm loading function simply became:

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

huangapple
  • 本文由 发表于 2021年11月10日 23:00:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/69915566.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定