4

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 (
    "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
}

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 "$(go env GOROOT)/misc/wasm/wasm_exec.js" .

My HTML file is:

<!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>

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("http://localhost:8080/www/main.wasm"),
        go.importObject
    );
    go.run(result.instance);
}
init();

The server code is:

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)

}

The output i got is:

enter image description here

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("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
}

Compliled it as:

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

And server.go as:

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)
    }))
}

And the JS code as:

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));

And the output i got is: enter image description here

5
  • related: stackoverflow.com/questions/67978442/go-wasm-export-functions/… Commented Nov 10, 2021 at 15:08
  • The function is available after the wasm main runs. Try using a script embedded in the body to start the wasm, instead of using an async function. Commented Nov 10, 2021 at 15:09
  • @blackgreen kindly see my update Commented Nov 10, 2021 at 16:18
  • @BurakSerdar kindly see my update Commented Nov 10, 2021 at 16:18
  • @HasanAYousef I think the problem after the update is that you don't wait for your setup Promises before calling exports.multiply. If you move the call to multiply() into the .then functions right after the calls to go.run, it might work. Commented Nov 10, 2021 at 17:18

1 Answer 1

3

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 === 'complete') {
  // The page is fully loaded
}

// or

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

So, as wasm initiation function in my code is async I used the below in 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>

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("http://localhost:8080/www/main.wasm"),
        go.importObject
    );
    go.run(result.instance); 
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.