从JavaScript调用函数

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

Calling a function from JavaScript

问题

尝试理解Go中的WebAssembly(wasm),所以我写了以下代码:

  1. 操纵DOM
  2. 调用JS函数
  3. 定义一个可以被JS调用的函数

前两步都没问题,但最后一步不像预期那样工作,因为我得到了JavaScript错误"function undefined",我的代码如下,我在函数sub中遇到了问题:

  1. package main
  2. import (
  3. "syscall/js"
  4. )
  5. // func sub(a, b float64) float64
  6. func sub(this js.Value, inputs []js.Value) interface{} {
  7. return inputs[0].Float() - inputs[1].Float()
  8. }
  9. func main() {
  10. 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
  11. js.Global().Set("sub", js.FuncOf(sub))
  12. alert := js.Global().Get("alert")
  13. alert.Invoke("Hi")
  14. println("Hello wasm")
  15. num := js.Global().Call("add", 3, 4)
  16. println(num.Int())
  17. document := js.Global().Get("document")
  18. h1 := document.Call("createElement", "h1")
  19. h1.Set("innerText", "This is H1")
  20. document.Get("body").Call("appendChild", h1)
  21. <-c // pause the execution so that the resources we create for JS keep available
  22. }

将其编译为wasm:

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

wasm_exec.js文件复制到与其相同的工作文件夹中:

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

我的HTML文件如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>WASM</title>
  5. <script src="http://localhost:8080/www/lib.js"></script>
  6. <!-- WASM -->
  7. <script src="http://localhost:8080/www/wasm_exec.js"></script>
  8. <script src="http://localhost:8080/www/loadWasm.js"></script>
  9. </head>
  10. <body>
  11. </body>
  12. <script>
  13. console.log(sub(5,3));
  14. </script>
  15. </html>

lib.js文件如下:

  1. function add(a, b){
  2. return a + b;
  3. }

loadWasm.js文件如下:

  1. async function init(){
  2. const go = new Go();
  3. const result = await WebAssembly.instantiateStreaming(
  4. fetch("http://localhost:8080/www/main.wasm"),
  5. go.importObject
  6. );
  7. go.run(result.instance);
  8. }
  9. init();

服务器代码如下:

  1. package main
  2. import (
  3. "fmt"
  4. "html/template"
  5. "net/http"
  6. )
  7. func wasmHandler() http.Handler {
  8. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  9. tmpl := template.Must(template.ParseFiles("www/home.html"))
  10. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  11. w.Header().Set("Access-Control-Allow-Origin", "*")
  12. err := tmpl.Execute(w, nil)
  13. if err != nil {
  14. fmt.Println(err)
  15. }
  16. })
  17. }
  18. func main() {
  19. fs := http.StripPrefix("/www/", http.FileServer(http.Dir("./www")))
  20. http.Handle("/www/", fs)
  21. http.Handle("/home", wasmHandler())
  22. http.ListenAndServe(":8080", nil)
  23. }

我得到的输出是:

从JavaScript调用函数

更新

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

  1. //wasm.go
  2. package main
  3. // This calls a JS function from Go.
  4. func main() {
  5. println("adding two numbers:", add(2, 3)) // expecting 5
  6. }
  7. // module from JavaScript.
  8. func add(x, y int) int
  9. //export multiply
  10. func multiply(x, y int) int {
  11. return x * y
  12. }

将其编译为:

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

server.go如下:

  1. package main
  2. import (
  3. "log"
  4. "net/http"
  5. "strings"
  6. )
  7. const dir = "./www"
  8. func main() {
  9. fs := http.FileServer(http.Dir(dir))
  10. log.Print("Serving " + dir + " on http://localhost:8080")
  11. http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
  12. resp.Header().Add("Cache-Control", "no-cache")
  13. if strings.HasSuffix(req.URL.Path, ".wasm") {
  14. resp.Header().Set("content-type", "application/wasm")
  15. }
  16. fs.ServeHTTP(resp, req)
  17. }))
  18. }

JS代码如下:

  1. const go = new Go(); // Defined in wasm_exec.js
  2. go.importObject.env = {
  3. 'main.add': function(x, y) {
  4. return x + y
  5. }
  6. // ... other functions
  7. }
  8. const WASM_URL = 'main.wasm';
  9. var wasm;
  10. if ('instantiateStreaming' in WebAssembly) {
  11. WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
  12. wasm = obj.instance;
  13. go.run(wasm);
  14. })
  15. } else {
  16. fetch(WASM_URL).then(resp =>
  17. resp.arrayBuffer()
  18. ).then(bytes =>
  19. WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
  20. wasm = obj.instance;
  21. go.run(wasm);
  22. })
  23. )
  24. }
  25. // Calling the multiply function:
  26. 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

  1. package main
  2. import (
  3. &quot;syscall/js&quot;
  4. )
  5. // func sub(a, b float64) float64
  6. func sub(this js.Value, inputs []js.Value) interface{} {
  7. return inputs[0].Float() - inputs[1].Float()
  8. }
  9. func main() {
  10. 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
  11. js.Global().Set(&quot;sub&quot;, js.FuncOf(sub))
  12. alert := js.Global().Get(&quot;alert&quot;)
  13. alert.Invoke(&quot;Hi&quot;)
  14. println(&quot;Hello wasm&quot;)
  15. num := js.Global().Call(&quot;add&quot;, 3, 4)
  16. println(num.Int())
  17. document := js.Global().Get(&quot;document&quot;)
  18. h1 := document.Call(&quot;createElement&quot;, &quot;h1&quot;)
  19. h1.Set(&quot;innerText&quot;, &quot;This is H1&quot;)
  20. document.Get(&quot;body&quot;).Call(&quot;appendChild&quot;, h1)
  21. &lt;-c // pause the execution so that the resources we create for JS keep available
  22. }

compiled it to wasm as:

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

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

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

My HTML file is:

  1. &lt;!DOCTYPE html&gt;
  2. &lt;html lang=&quot;en&quot;&gt;
  3. &lt;head&gt;
  4. &lt;title&gt;WASM&lt;/title&gt;
  5. &lt;script src=&quot;http://localhost:8080/www/lib.js&quot;&gt;&lt;/script&gt;
  6. &lt;!-- WASM --&gt;
  7. &lt;script src=&quot;http://localhost:8080/www/wasm_exec.js&quot;&gt;&lt;/script&gt;
  8. &lt;script src=&quot;http://localhost:8080/www/loadWasm.js&quot;&gt;&lt;/script&gt;
  9. &lt;/head&gt;
  10. &lt;body&gt;
  11. &lt;/body&gt;
  12. &lt;script&gt;
  13. console.log(sub(5,3));
  14. &lt;/script&gt;
  15. &lt;/html&gt;

The lib.js is:

  1. function add(a, b){
  2. return a + b;
  3. }

The loadWasm.js is:

  1. async function init(){
  2. const go = new Go();
  3. const result = await WebAssembly.instantiateStreaming(
  4. fetch(&quot;http://localhost:8080/www/main.wasm&quot;),
  5. go.importObject
  6. );
  7. go.run(result.instance);
  8. }
  9. init();

The server code is:

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;html/template&quot;
  5. &quot;net/http&quot;
  6. )
  7. func wasmHandler() http.Handler {
  8. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  9. tmpl := template.Must(template.ParseFiles(&quot;www/home.html&quot;))
  10. w.Header().Set(&quot;Content-Type&quot;, &quot;text/html; charset=utf-8&quot;)
  11. w.Header().Set(&quot;Access-Control-Allow-Origin&quot;, &quot;*&quot;)
  12. err := tmpl.Execute(w, nil)
  13. if err != nil {
  14. fmt.Println(err)
  15. }
  16. })
  17. }
  18. func main() {
  19. fs := http.StripPrefix(&quot;/www/&quot;, http.FileServer(http.Dir(&quot;./www&quot;)))
  20. http.Handle(&quot;/www/&quot;, fs)
  21. http.Handle(&quot;/home&quot;, wasmHandler())
  22. http.ListenAndServe(&quot;:8080&quot;, nil)
  23. }

The output i got is:

从JavaScript调用函数

UPDATE

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

  1. //wasm.go
  2. package main
  3. // This calls a JS function from Go.
  4. func main() {
  5. println(&quot;adding two numbers:&quot;, add(2, 3)) // expecting 5
  6. }
  7. // module from JavaScript.
  8. func add(x, y int) int
  9. //export multiply
  10. func multiply(x, y int) int {
  11. return x * y
  12. }

Compliled it as:

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

And server.go as:

  1. package main
  2. import (
  3. &quot;log&quot;
  4. &quot;net/http&quot;
  5. &quot;strings&quot;
  6. )
  7. const dir = &quot;./www&quot;
  8. func main() {
  9. fs := http.FileServer(http.Dir(dir))
  10. log.Print(&quot;Serving &quot; + dir + &quot; on http://localhost:8080&quot;)
  11. http.ListenAndServe(&quot;:8080&quot;, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
  12. resp.Header().Add(&quot;Cache-Control&quot;, &quot;no-cache&quot;)
  13. if strings.HasSuffix(req.URL.Path, &quot;.wasm&quot;) {
  14. resp.Header().Set(&quot;content-type&quot;, &quot;application/wasm&quot;)
  15. }
  16. fs.ServeHTTP(resp, req)
  17. }))
  18. }

And the JS code as:

  1. const go = new Go(); // Defined in wasm_exec.js
  2. go.importObject.env = {
  3. &#39;main.add&#39;: function(x, y) {
  4. return x + y
  5. }
  6. // ... other functions
  7. }
  8. const WASM_URL = &#39;main.wasm&#39;;
  9. var wasm;
  10. if (&#39;instantiateStreaming&#39; in WebAssembly) {
  11. WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
  12. wasm = obj.instance;
  13. go.run(wasm);
  14. })
  15. } else {
  16. fetch(WASM_URL).then(resp =&gt;
  17. resp.arrayBuffer()
  18. ).then(bytes =&gt;
  19. WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
  20. wasm = obj.instance;
  21. go.run(wasm);
  22. })
  23. )
  24. }
  25. // Calling the multiply function:
  26. console.log(&#39;multiplied two numbers:&#39;, exports.multiply(5, 3));

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

答案1

得分: 3

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

  1. if (document.readyState === 'complete') {
  2. // 页面已完全加载
  3. }
  4. // 或者
  5. document.onreadystatechange = () => {
  6. if (document.readyState === 'complete') {
  7. // 文档已准备好
  8. }
  9. };

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

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>WASM</title>
  5. <!-- WASM -->
  6. <script src="http://localhost:8080/www/wasm_exec.js"></script>
  7. <script src="http://localhost:8080/www/loadWasm.js"></script>
  8. </head>
  9. <body>
  10. </body>
  11. <script>
  12. (async () => {
  13. try {
  14. await init();
  15. alert("Wasm已加载");
  16. console.log(multiply(5, 3));
  17. } catch (e) {
  18. console.log(e);
  19. }
  20. })();
  21. /***** OR ****/
  22. (async () => {
  23. await init();
  24. alert("Wasm已加载");
  25. console.log(multiply(5, 3));
  26. })().catch(e => {
  27. console.log(e);
  28. });
  29. /*************/
  30. </script>
  31. </html>

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

wasm加载函数简单地变成了:

  1. async function init(){
  2. const go = new Go();
  3. const result = await WebAssembly.instantiateStreaming(
  4. fetch("http://localhost:8080/www/main.wasm"),
  5. go.importObject
  6. );
  7. go.run(result.instance);
  8. }
英文:

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:

  1. if (document.readyState === &#39;complete&#39;) {
  2. // The page is fully loaded
  3. }
  4. // or
  5. document.onreadystatechange = () =&gt; {
  6. if (document.readyState === &#39;complete&#39;) {
  7. // document ready
  8. }
  9. };

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

  1. &lt;!DOCTYPE html&gt;
  2. &lt;html lang=&quot;en&quot;&gt;
  3. &lt;head&gt;
  4. &lt;title&gt;WASM&lt;/title&gt;
  5. &lt;!-- WASM --&gt;
  6. &lt;script src=&quot;http://localhost:8080/www/wasm_exec.js&quot;&gt;&lt;/script&gt;
  7. &lt;script src=&quot;http://localhost:8080/www/loadWasm.js&quot;&gt;&lt;/script&gt;
  8. &lt;/head&gt;
  9. &lt;body&gt;
  10. &lt;/body&gt;
  11. &lt;script&gt;
  12. (async () =&gt; {
  13. try {
  14. await init();
  15. alert(&quot;Wasm had been loaded&quot;)
  16. console.log(multiply(5, 3));
  17. } catch (e) {
  18. console.log(e);
  19. }
  20. })();
  21. /***** OR ****/
  22. (async () =&gt; {
  23. await init();
  24. alert(&quot;Wasm had been loaded&quot;)
  25. console.log(multiply(5, 3));
  26. })().catch(e =&gt; {
  27. console.log(e);
  28. });
  29. /*************/
  30. &lt;/script&gt;
  31. &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:

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

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:

确定