虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

【WebAssembly連載第三回】改めてwasm-bindgenを使ってWebAssemblyでのJavaScript APIを操作してみる

本記事は「WebAssembly連載」の第三回目の記事です。

皆さんこんにちは。虎の穴ラボのY.Fです。

前回記事では、web-sysを使ってWebAssemblyでDOM APIを利用する方法について紹介しました。

toranoana-lab.hatenablog.com

今回は、もう一つの関連クレートであるjs-sysを使って、WebAssemblyからJavaScript APIを利用してみたいと思います。

利用技術

  • Rust 1.65.0
  • wasm-bindgen 最新版
    • JavaScriptとRust(WebAssembly)とのデータ等のやり取りを簡単にしてくれるクレート(ライブラリ)
  • js-sys最新版
    • RustからJavaScript APIを呼び出すためのライブラリ
  • wasm-pack 最新版
    • WebAssemblyで出来たファイルをライブラリとして整えてくれるツール

js-sysが加わっただけで特に前回と変わりありません。

ドキュメントは以下になります rustwasm.github.io

ドキュメントによれば、ECMAScript標準に記載されているグローバルオブジェクトのみに対応しているとのことです。

以下で、いくつかAPIやオブジェクトを扱ってみたいと思います。

globalオブジェクト

以下でそのままglobalオブジェクトを取得できます

let this = js_sys::global();
console::log_1(&this.into());

ブラウザで実行するとWindowオブジェクトが取得できると思います。

js_sys::global(); の戻り値は js_sys::Object となっており、こちらもjs-sysで提供されています。

JavaScriptとやり取りしてみる

上記で js_sys::Object としてJavaScriptのObjectを扱えることを紹介しました。これを使ってJavaScript側で定義された関数をWebAssemblyから利用してみたいと思います。

まずはindex.htmlに以下のようにグローバルな関数を追加します

<script>
  function hoge(obj) {
    console.log(obj);
  }
  function fuga() {
    return { hello: "world" };
  }
</script>

これをWebAssemblyから利用します。まずは extern 宣言に利用したい関数を書きます。alertはもともと定義されていたものになります。

#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);
    fn hoge(obj: &js_sys::Object);
    fn fuga() -> js_sys::Object;
}

実際に使うには普通に hoge 及び fuga を呼び出すだけです。

let obj = fuga();
hoge(&obj);

Promise

Promiseも扱えます。JavaScript側で定義された非同期関数をRustから呼んでみようと思います。

まずは素朴に then を扱ってみます。JavaScript側に前回記事でも利用したAPIをコールする関数を作ります。

async function callApi() {
  return fetch("https://reqres.in/api/users?page=1");
}

Rustから使ってみます。今回はextern宣言は割愛します。

let promise = callApi();
let callback = Closure::<dyn FnMut(JsValue)>::new(move |res: JsValue| {
    let callback2 = Closure::<dyn FnMut(JsValue)>::new(move |json: JsValue| {
        console::log_1(&json.into());
    });

    let response: Response = res.dyn_into().expect("response is not `Response`");
    response.json().expect("invalid json data").then(&callback2);
    callback2.forget();
});

promise.then(&callback);
callback.forget();

関数の中にコールバック関数を書くコールバック地獄の様相になってしまいました。

これは、調べた中でPromiseチェーンをRust側で行う方法が不明だったためです。Closureが戻り値を持つことができなさそうだったので、このようにしました。

次に、前回記事でも紹介した future を使ってよりRustっぽい非同期処理に落とし込んでみます。

spawn_local(async move {
    let promise2 = callApi();
    let resp_value = JsFuture::from(promise2).await.unwrap();
    let resp: Response = resp_value.dyn_into().unwrap();

    let json_value = JsFuture::from(resp.json().unwrap()).await.unwrap();
    console::log_1(&json_value.into());
});

サンプル用のコードなのでunwrapにしてます。

spawn_local 等は前回記事でも同様なので、そちらご確認ください。 こちらのほうが普通の処理のようにかけるので、Rust側から非同期処理を扱う場合はこちらのほうが良さそうです。

まとめ

少し短いですが、今回の記事ではjs-sysを通してJavaScriptのAPIや変数を扱う方法を紹介しました。 JavaScriptの値を扱いたければ、JavaScript側にコードを書く方法も取れるので、前回紹介した、DOM APIを扱うweb-sysよりは使い所が難しそうだなーと思いました。

これで、wasm-bindgen絡みのライブラリを紹介しましたので、次回記事では実際になにか作ってみたいと思います。

P.S.

採用

虎の穴では一緒に働く仲間を募集中です!
この記事を読んで、興味を持っていただけた方はぜひ弊社の採用情報をご覧下さい。
yumenosora.co.jp

LINEスタンプ

エンジニア専用のメイドちゃんスタンプが完成しました!
「あの場面」で思わず使いたくなるようなスタンプから、日常で役立つスタンプを合計40個用意しました。
エンジニアの皆さん、エンジニアでない方もぜひスタンプを確認してみてください。 store.line.me