本記事は「WebAssembly連載」の第三回目の記事です。
皆さんこんにちは。虎の穴ラボのY.Fです。
前回記事では、web-sysを使ってWebAssemblyでDOM APIを利用する方法について紹介しました。
今回は、もう一つの関連クレートである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