1use std::time::Duration;
2use std::future::Future;
3
4#[cfg(not(target_arch = "wasm32"))]
9pub use tokio::time::error::Elapsed;
10
11#[cfg(target_arch = "wasm32")]
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct Elapsed;
14
15#[cfg(target_arch = "wasm32")]
16impl std::fmt::Display for Elapsed {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 f.write_str("deadline has elapsed")
19 }
20}
21
22#[cfg(target_arch = "wasm32")]
23impl std::error::Error for Elapsed {}
24
25pub async fn timeout<F: Future>(duration: Duration, future: F)
30 -> Result<F::Output, Elapsed>
31{
32 #[cfg(not(target_arch = "wasm32"))]
33 {
34 tokio::time::timeout(duration, future).await
35 }
36
37 #[cfg(target_arch = "wasm32")]
38 {
39 use std::pin::pin;
40 use futures::future::{select, Either};
41
42 let s = pin!(crate::sleep(duration));
43 let f = pin!(future);
44 match select(f, s).await {
45 Either::Left((out, _)) => Ok(out),
46 Either::Right(((), _)) => Err(Elapsed),
47 }
48 }
49}
50
51#[cfg(test)]
52mod test {
53 use super::*;
54
55 #[cfg(target_arch = "wasm32")]
56 use wasm_bindgen_test::wasm_bindgen_test;
57 #[cfg(target_arch = "wasm32")]
58 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
59
60 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
62 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
63 async fn returns_the_output_of_a_fast_future() {
64 let out = timeout(Duration::from_secs(5), async { "done" }).await
65 .expect("an immediate future cannot time out");
66 assert_eq!("done", out);
67 }
68
69 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
71 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
72 async fn returns_the_output_of_a_future_that_sleeps_briefly() {
73 let out = timeout(Duration::from_secs(5), async {
74 crate::sleep(Duration::from_millis(10)).await;
75 7
76 }).await.expect("a brief sleep fits well within the deadline");
77 assert_eq!(7, out);
78 }
79
80 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
82 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
83 async fn errors_when_the_future_outlives_the_deadline() {
84 timeout(Duration::from_millis(50), async {
85 crate::sleep(Duration::from_secs(60)).await;
86 }).await.expect_err("the deadline should have fired first");
87 }
88
89 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
91 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
92 async fn errors_on_a_zero_deadline() {
93 timeout(Duration::ZERO, async {
94 crate::sleep(Duration::from_secs(60)).await;
95 }).await.expect_err("a zero deadline is already past");
96 }
97
98 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
100 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
101 async fn elapsed_error_mentions_the_deadline() {
102 let err = timeout(Duration::from_millis(10), async {
103 crate::sleep(Duration::from_secs(60)).await;
104 }).await.unwrap_err();
105 assert!(err.to_string().contains("elapsed"), "{}", err);
106 }
107}
108