Promise / Futures

uniffi-bindgen-react-native provides support of Futures/async fn. These are mapped to Javascript Promises. More information can be found in the uniffi book.

This example is taken from the above link:

#![allow(unused)]
fn main() {
use std::time::Duration;
use async_std::future::{timeout, pending};

/// Async function that says something after a certain time.
#[uniffi::export]
pub async fn say_after(ms: u64, who: String) -> String {
    let never = pending::<()>();
    timeout(Duration::from_millis(ms), never).await.unwrap_err();
    format!("Hello, {who}!")
}
}

It can be called from Typescript:

// Wait 1 second for Hello, World!
const message = await sayAfter(1000n, "World");

You can see this in action in the futures-example fixture, and the more complete futures fixture.

Passing Promises across the FFI

There is no support for passing a Promise or Future as an argument or error, in either direction.

Task cancellation

Internally, uniffi-rs generates a cancel function for each Future. On calling it, the Future is dropped.

This is accessible for every function that returns a Promise by passing an optional { signal: AbortSignal } option bag as the final argument.

Using the same Rust as above:

#![allow(unused)]
fn main() {
use std::time::Duration;
use async_std::future::{timeout, pending};

/// Async function that says something after a certain time.
#[uniffi::export]
pub async fn say_after(ms: u64, who: String) -> String {
    let never = pending::<()>();
    timeout(Duration::from_millis(ms), never).await.unwrap_err();
    format!("Hello, {who}!")
}
}

It can be used from Typescript, either without an AbortSignal as above, or with one passed as the final argument:

const abortController = new AbortController();
setTimeout(() => abortController.abort(), 1000);
try {
    // Wait 1 hour for Hello, World!
    const message = await sayAfter(60 * 60 * 1000, "World", { signal: abortController.signal });
    console.log(message);
} catch (e: any) {
    e instanceof Error; // true
    e.name === "AbortError"; // true
}

This example calls into the say_after function, and the Rust would wait for 1 hour before returning. However, the abortController has its abort method called after 1 second.

Warning

Task cancellation for one language is… complicated. For FFIs, it is a small but important source of impedence mismatches between languages.

The uniffi-rs docs suggest that:

You should build your cancellation in a separate, library specific channel; for example, exposing a cancel() method that sets a flag that the library checks periodically.

While uniffi-rs is recommending this, uniffi-bindgen-react-native— as a foreign-language binding to the uniffi-rs code— does so too.

However, while uniffi-rs exposes rust_future_cancel function, uniffi-bindgen-react-native— as a foreign-language binding to the uniffi-rs code— does so too.