Working with a JS Promise and a Rust Future

Many APIs on the web work with a Promise, such as an async function in JS. Naturally you'll probably want to interoperate with them from Rust! To do that you can use the wasm-bindgen-futures crate as well as Rust async functions.

The first thing you might encounter is the need for working with a Promise. For this you'll want to use js_sys::Promise. Once you've got one of those values you can convert that value to wasm_bindgen_futures::JsFuture. This type implements the std::future::Future trait which allows naturally using it in an async function. For example:


# #![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)
}
#}

Here we can see how converting a Promise to Rust creates a impl Future<Output = Result<JsValue, JsValue>>. This corresponds to then and catch in JS where a successful promise becomes Ok and an erroneous promise becomes Err.

You can also import a JS async function directly with a extern "C" block, and the promise will be converted to a future automatically. For now the return type must be JsValue or no return at all:


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

The async can be combined with the catch attribute to manage errors from the 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>;
}
#}

Next up you'll probably want to export a Rust function to JS that returns a promise. To do this you can use an async function and #[wasm_bindgen]:


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

When invoked from JS the foo function here will return a Promise, so you can import this as:

import { foo } from "my-module";

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

Return values of async fn

When using an async fn in Rust and exporting it to JS there's some restrictions on the return type. The return value of an exported Rust function will eventually become Result<JsValue, JsValue> where Ok turns into a successfully resolved promise and Err is equivalent to throwing an exception.

The following types are supported as return types from an async fn:

  • () - turns into a successful undefined in JS
  • T: Into<JsValue> - turns into a successful JS value
  • Result<(), E: Into<JsValue>> - if Ok(()) turns into a successful undefined and otherwise turns into a failed promise with E converted to a JS value
  • Result<T: Into<JsValue>, E: Into<JsValue>> - like the previous case except both data payloads are converted into a JsValue.

Note that many types implement being converted into a JsValue, such as all imported types via #[wasm_bindgen] (aka those in js-sys or web-sys), primitives like u32, and all exported #[wasm_bindgen] types. In general, you should be able to write code without having too many explicit conversions, and the macro should take care of the rest!

Using wasm-bindgen-futures

The wasm-bindgen-futures crate bridges the gap between JavaScript Promises and Rust Futures. Its JsFuture type provides conversion from a JavaScript Promise into a Rust Future, and its future_to_promise function converts a Rust Future into a JavaScript Promise and schedules it to be driven to completion.

Learn more:

Compatibility with versions of Future

The current crate on crates.io, wasm-bindgen-futures 0.4.*, supports std::future::Future and async/await in Rust. This typically requires Rust 1.39.0+ (as of this writing on 2019-09-05 it's the nightly channel of Rust).

If you're using the Future trait from the futures 0.1.* crate then you'll want to use the 0.3.* track of wasm-bindgen-futures on crates.io.