Promise / Futures
uniffi-bindgen-react-native
provides support of Future
s/async fn
. These are mapped to Javascript Promise
s. 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.
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.