How to implement expiration timeout for HTML5

There are some crates around for using JavaScript timers, but I want a cross-platform way that uses wasm_bindgen for the browser (plus js_sys, web_sys and wasm_bindgen_futures and proper animation intervals) and fits with tokio, so that I have a solid abstraction layer over both. Anyway, I think this is trouble:

(Like tokio::time::timeout, but for the browser.)

rialight_util::timing internal submodule: platform::browser_runtime

pub async fn timeout<F: Future + Send + 'static>(duration: Duration, future: F) -> Result<(), super::ElapsedError> {
    let mut completed = Arc::new(RwLock::new(false));
        let completed = Arc::clone(&mut completed);
        async move {
            *completed.write().unwrap() = true;
    if * {
        return Ok(());

Any recommendation for what to do?

Full source:

The wait is implemented for instance, but it's based on a promise and I'm not sure how to poll properly before expiration

I got this idea:

struct Timeout<F: Future + Send + 'static> {
    pub operation: wasm_bindgen_futures::JsFuture,
    pub resolving_to: F,
    pub expired: Arc<RwLock<bool>>,

impl<F: Future + Send + 'static> Future for Timeout<F> {
    type Output = Result<(), super::ElapsedError>;
    fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
        if * {
        } else {

Any idea on how to poll with a value?

This doesn't make sense :sweat_smile: I'd like to race by the way...

I'm getting this strange error:

error[E0308]: mismatched types
   --> src\util\src\timing\platform_based\
127 |         (async { future.await; }),
    |          ----------------------- the expected `async` block
128 |         (async { wait(duration).await; }),
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `async` block, found a different `async` block
    = note: expected `async` block `[async block@src\util\src\timing\platform_based\ 127:33]`
               found `async` block `[async block@src\util\src\timing\platform_based\ 128:41]`


pub async fn timeout<F: Future<Output = ()> + Send + 'static>(duration: Duration, future: F) -> Result<(), super::ElapsedError> {
    let (_, i) = future_race([
        (async { future.await; }),
        (async { wait(duration).await; }),

    match i {
        0 => Ok(()),
        1 => Err(super::ElapsedError),

You're trying to pass an array of futures, but the concrete type of every async block is a unique opaque type. You need type erasure of some sort to store multiple async blocks in an array

1 Like

I got it... The problem is that I'm using wasm_bindgen_futures::JsFuture to convert a JavaScript promise to a Rust future.

JsFuture doesn't implement Send, therefore the wait(...) call has no compatibility with the timeout future...

If there were a way to remove that Send bound from tokio::time::timeout's given future too, it might be easy to get this working.

It looks like I got no compilation error when removing Send from given future here:

pub async fn timeout<F>(duration: Duration, future: F) -> Result<(), ElapsedError>
    F: Future<Output = ()> + 'static,
    #[cfg(feature = "rialight_default_export")] {
        return match tokio::time::timeout(duration, future).await {
            Err(_) => Err(ElapsedError),
            Ok(_) => Ok(()),
    #[cfg(feature = "rialight_browser_export")] {
    #[cfg(not(any(feature = "rialight_default_export", feature = "rialight_browser_export")))] {
        let _ = (duration, future);
        panic!("Incorrectly configured Rialight runtime");

However the issue above has not been resolved yet even removing Send.

I get it though when you mean "opaque". Even removing the Send bounds, these futures are still of separate type, right? Hmm

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.