Skip to main content

kithara_platform/
time.rs

1#[cfg(target_arch = "wasm32")]
2use std::pin::pin;
3pub use std::time::Duration;
4
5#[cfg(target_arch = "wasm32")]
6use futures::future::{self as future_util, Either};
7#[cfg(target_arch = "wasm32")]
8use js_sys::{Function, Promise, Reflect, global};
9#[cfg(not(target_arch = "wasm32"))]
10use tokio_alias::time as tokio_time;
11#[cfg(not(target_arch = "wasm32"))]
12pub use tokio_time::sleep;
13#[cfg(not(target_arch = "wasm32"))]
14use tokio_with_wasm::alias as tokio_alias;
15#[cfg(target_arch = "wasm32")]
16use wasm_bindgen::{JsCast, JsValue};
17#[cfg(target_arch = "wasm32")]
18use wasm_bindgen_futures::JsFuture;
19pub use web_time::Instant;
20
21#[cfg(target_arch = "wasm32")]
22pub async fn sleep(duration: Duration) {
23    let ms = i32::try_from(duration.as_millis()).unwrap_or(i32::MAX);
24    let promise = Promise::new(&mut |resolve, _| {
25        let set_timeout: Function = Reflect::get(&global(), &JsValue::from_str("setTimeout"))
26            .expect("BUG: setTimeout is a standard browser global, must always exist")
27            .unchecked_into();
28        let _ = set_timeout.call2(&JsValue::UNDEFINED, &resolve, &JsValue::from(ms));
29    });
30    let _ = JsFuture::from(promise).await;
31}
32
33/// Error returned when an async operation exceeds its deadline.
34#[derive(Debug)]
35pub struct TimeoutError;
36
37impl std::fmt::Display for TimeoutError {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        f.write_str("operation timed out")
40    }
41}
42
43impl std::error::Error for TimeoutError {}
44
45/// Await `future` with a deadline.
46///
47/// On native: delegates to [`tokio::time::timeout`].
48/// On wasm32: races the future against a `setTimeout`-based timer.
49///
50/// # Errors
51///
52/// Returns [`TimeoutError`] if the future does not complete within `duration`.
53#[cfg(not(target_arch = "wasm32"))]
54pub async fn timeout<F>(duration: Duration, future: F) -> Result<F::Output, TimeoutError>
55where
56    F: Future,
57{
58    tokio_time::timeout(duration, future)
59        .await
60        .map_err(|_| TimeoutError)
61}
62
63#[cfg(target_arch = "wasm32")]
64pub async fn timeout<F>(duration: Duration, future: F) -> Result<F::Output, TimeoutError>
65where
66    F: Future,
67{
68    let deadline = pin!(sleep(duration));
69    let work = pin!(future);
70
71    match future_util::select(work, deadline).await {
72        Either::Left((output, _)) => Ok(output),
73        Either::Right(((), _)) => Err(TimeoutError),
74    }
75}