こんにちは。Goの話です。
JavaScript から Go(Wasm) の関数を呼び出す方法を探りました。
【よくあるやり方】 JavaScriptのグローバル変数を使う
通常、JavaScript から Go(Wasm) の関数を呼ぶにはグローバル変数を使います。例えば次のようなコードです
import ( "syscall/js" ) func hasPrefix(_ js.Value, args []js.Value) any { // 処理 } func main() { ch := make(chan struct{}) js.Global().Set("hasPrefixEntry", js.FuncOf(hasPrefix)) <-ch }
この例では、JavaScriptに hasPrefixEntry というグローバル変数を定義してます。hasPrefixEntry は関数で、内部的に Go(Wasm) の hasPrefix 関数を呼びます。
課題感
上記のコードには次のような課題があります
- グローバル変数を使用している
- Go(Wasm) がグローバル変数を宣言している
当たり前ですが JavaScript でグローバル変数を扱いたくありません。
また、Go(Wasm) はグローバル変数を宣言しています。JavaScript にとって Go(Wasm) はブラックボックスで認知しづらいです。グローバル変数の宣言は JavaScript で行いたいものです。
ここでは 2. の解決を目指します。
register関数を用意する
Go(Wasm) では JavaScript のグローバルオブジェクトにアクセスできます。ポイントは、グローバル変数の取得や関数の実行も出来る、ということです。
今回は wasmRegisterFn という関数をグローバルに用意しました
const routes = {} // グローバル globalThis.wasmRegisterFn = (name: string, wasmFn: (arg: any) => any) => { routes[name] = wasmFn // wasmFn を name として登録 } const go = new Go() const res = await WebAssembly.instantiateStreaming(fetch('/main.wasm'), go.importObject) go.run(res.instance)
wasmRegisterFn は routes というローカル変数に name と wasmFn を格納します
これをGoから呼び出します
import ( "strings" "syscall/js" ) func hasPrefix(_ js.Value, args []js.Value) interface{} { text := args[0].Get("text").String() prefix := args[0].Get("prefix").String() return js.ValueOf(strings.HasPrefix(text, prefix)) } func main() { ch := make(chan struct{}) // registerする js.Global().Get("wasmRegisterFn").Invoke("hasPrefix", js.FuncOf(hasPrefix)) <-ch }
Go(Wasm) から wasmRegisterFn を呼び hasPrefix という関数を紐付けてます。
Go(Wasm) の関数を呼ぶには次のように書きます
const result = routes.hasPrefix({ text: 'aaa', prefix: 'b' }) console.log(result) // false
以上で、ローカル変数に紐付け&呼び出しが出来ました。
【補足】 グローバル変数を使わない手段
そもそもグローバル変数を使わない手段もあります。tinygo の export です。ESModule のようなイメージで、Goの関数を named export できます。これも有用なのですが、string を返せない、という難点があります。int は返せるので何とか出来なくはないですが、針の糸を通すようなコードになるかもしれません