devlog

JavaScript から Go(Wasm) の関数を呼び出す。ローカル変数に紐付ける

2023-05-29

こんにちは。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 関数を呼びます。

課題感

上記のコードには次のような課題があります

  1. グローバル変数を使用している
  2. 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 は返せるので何とか出来なくはないですが、針の糸を通すようなコードになるかもしれません

  • 作成日
    2023-05-29
  • 更新日
    2023-05-29