使用 JS Promise 與 Rust Future

網路上許多 API 都使用 Promise,例如 JS 中的 async 函式。自然地,您可能會想從 Rust 與它們進行互動!為此,您可以使用 wasm-bindgen-futures crate 以及 Rust async 函式。

您可能遇到的第一件事是需要使用 Promise。為此,您會想使用 js_sys::Promise。一旦您取得其中一個值,您可以將該值轉換為 wasm_bindgen_futures::JsFuture。此類型實作了 std::future::Future 特徵,這使得在 async 函式中自然地使用它。例如:


# #![allow(unused_variables)]
#fn main() {
async fn get_from_js() -> Result<JsValue, JsValue> {
    let promise = js_sys::Promise::resolve(&42.into());
    let result = wasm_bindgen_futures::JsFuture::from(promise).await?;
    Ok(result)
}
#}

在這裡,我們可以看見將 Promise 轉換為 Rust 會產生一個 impl Future<Output = Result<JsValue, JsValue>>。這對應於 JS 中的 thencatch,其中成功的 promise 變成 Ok,而錯誤的 promise 變成 Err

您也可以直接使用 extern "C" 區塊匯入 JS async 函式,並且 promise 會自動轉換為 future。目前,回傳類型必須是 JsValue 或完全不回傳任何值


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    async fn async_func_1_ret_number() -> JsValue;
    async fn async_func_2();
}

async fn get_from_js() -> f64 {
    async_func_1_ret_number().await.as_f64().unwrap_or(0.0)
}
#}

async 可以與 catch 屬性結合使用,以管理來自 JS promise 的錯誤


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(catch)]
    async fn async_func_3() -> Result<JsValue, JsValue>;
    #[wasm_bindgen(catch)]
    async fn async_func_4() -> Result<(), JsValue>;
}
#}

接下來,您可能想要將一個 Rust 函式匯出到 JS,該函式會回傳一個 promise。為此,您可以使用 async 函式和 #[wasm_bindgen]


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
pub async fn foo() {
    // ...
}
#}

從 JS 呼叫時,此處的 foo 函式會回傳一個 Promise,因此您可以將其匯入為

import { foo } from "my-module";

async function shim() {
    const result = await foo();
    // ...
}

async fn 的回傳值

在 Rust 中使用 async fn 並將其匯出到 JS 時,回傳類型有一些限制。匯出的 Rust 函式的回傳值最終會變成 Result<JsValue, JsValue>,其中 Ok 會變成成功解析的 promise,而 Err 相當於拋出例外狀況。

以下類型支援作為 async fn 的回傳類型

  • () - 在 JS 中變成成功的 undefined
  • T: Into<JsValue> - 變成成功的 JS 值
  • Result<(), E: Into<JsValue>> - 如果是 Ok(()),則變成成功的 undefined,否則變成失敗的 promise,其中 E 會轉換為 JS 值
  • Result<T: Into<JsValue>, E: Into<JsValue>> - 與先前的案例類似,只是兩個資料酬載都會轉換為 JsValue

請注意,許多類型都實作了轉換為 JsValue 的功能,例如所有透過 #[wasm_bindgen] 匯入的類型(也就是 js-sysweb-sys 中的類型)、諸如 u32 之類的原始類型,以及所有匯出的 #[wasm_bindgen] 類型。一般而言,您應該能夠撰寫程式碼而無需太多明確的轉換,並且巨集會處理其餘部分!

使用 wasm-bindgen-futures

wasm-bindgen-futures crate 彌合了 JavaScript Promise 和 Rust Future 之間的差距。其 JsFuture 類型提供了將 JavaScript Promise 轉換為 Rust Future 的功能,而其 future_to_promise 函式則將 Rust Future 轉換為 JavaScript Promise,並排程它來驅動完成。

深入瞭解

Future 版本的相容性

crates.io 上的目前 crate wasm-bindgen-futures 0.4.* 支援 Rust 中的 std::future::Futureasync/await。這通常需要 Rust 1.39.0+ (截至 2019-09-05,它還是 Rust 的 nightly 管道)。

如果您使用的是 futures 0.1.* crate 中的 Future 特徵,則您會想使用 crates.io 上 wasm-bindgen-futures0.3.* 版本。