sweet_web/dom_utils/
future_timeout.rs

1use crate::timeout_reject;
2use anyhow::Result;
3use js_sys::Array;
4use js_sys::Promise;
5use std::cell::RefCell;
6use std::future::Future;
7use std::rc::Rc;
8use std::time::Duration;
9use wasm_bindgen::JsValue;
10use wasm_bindgen::UnwrapThrowExt;
11use wasm_bindgen_futures::spawn_local;
12use wasm_bindgen_futures::JsFuture;
13
14
15pub async fn future_timeout<F, Fut, O>(fut: F, duration: Duration) -> Result<O>
16where
17	F: 'static + FnOnce() -> Fut,
18	Fut: Future<Output = O>,
19	O: 'static,
20{
21	let out = Rc::<RefCell<Option<O>>>::default();
22
23	let mut fut = Some(fut);
24	let out2 = out.clone();
25	let prom = Promise::new(&mut move |resolve, _reject| {
26		let fut = fut.take().unwrap_throw();
27		let out = out2.clone();
28		spawn_local(async move {
29			let result = fut().await;
30			*out.borrow_mut() = Some(result);
31			resolve.call0(&JsValue::NULL).unwrap();
32		});
33	});
34	let timeout = timeout_reject(duration);
35
36	let arr = Array::new();
37	arr.push(&prom);
38	arr.push(&timeout);
39
40	match JsFuture::from(Promise::race(&arr)).await {
41		Ok(_) => Ok(out.take().unwrap()),
42		Err(_) => Err(anyhow::anyhow!("Timeout")),
43	}
44}
45
46
47#[cfg(test)]
48#[cfg(target_arch = "wasm32")]
49mod test {
50	use crate::prelude::*;
51	use std::time::Duration;
52	use sweet_test as sweet;
53	use sweet_test::prelude::*;
54
55	#[sweet_test::test]
56	pub async fn works() {
57		let a = future_timeout(
58			|| async {
59				wait_for_millis(400).await;
60				39
61			},
62			Duration::from_millis(500),
63		)
64		.await
65		.unwrap();
66		expect(a).to_be(39);
67	}
68	#[sweet_test::test]
69	pub async fn times_out() {
70		let err = future_timeout(
71			|| async {
72				wait_for_millis(600).await;
73			},
74			Duration::from_millis(500),
75		)
76		.await;
77		expect(err).to_be_err_str("Timeout");
78	}
79}