使用 JS Promise 和 Rust Future

Web 上的许多 API 都使用 Promise,例如 JS 中的 async 函数。自然地,你可能希望从 Rust 中与它们进行交互!为此,你可以使用 wasm-bindgen-futures crate 以及 Rust async 函数。

你可能遇到的第一件事是需要使用 Promise。为此,你将需要使用 js_sys::Promise。一旦你有了其中一个值,你可以将该值转换为 wasm_bindgen_futures::JsFuture。此类型实现了 std::future::Future trait,这使得它可以自然地在 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 异步函数,并且 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)
}
#}

可以将 asynccatch 属性结合使用来管理来自 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>;
}
#}

接下来,你可能希望导出一个返回 promise 的 Rust 函数到 JS。为此,你可以使用 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 trait,那么你将需要使用 crates.io 上 wasm-bindgen-futures0.3.* track。