gloo_timers/
callback.rs

1//! Callback-style timer APIs.
2
3use js_sys::Function;
4use wasm_bindgen::prelude::*;
5use wasm_bindgen::{JsCast, JsValue};
6
7#[wasm_bindgen]
8extern "C" {
9    #[wasm_bindgen(js_name = "setTimeout", catch)]
10    fn set_timeout(handler: &Function, timeout: i32) -> Result<JsValue, JsValue>;
11
12    #[wasm_bindgen(js_name = "setInterval", catch)]
13    fn set_interval(handler: &Function, timeout: i32) -> Result<JsValue, JsValue>;
14
15    #[wasm_bindgen(js_name = "clearTimeout")]
16    fn clear_timeout(handle: JsValue) -> JsValue;
17
18    #[wasm_bindgen(js_name = "clearInterval")]
19    fn clear_interval(handle: JsValue) -> JsValue;
20}
21
22/// A scheduled timeout.
23///
24/// See `Timeout::new` for scheduling new timeouts.
25///
26/// Once scheduled, you can [`drop`] the [`Timeout`] to clear it or [`forget`](Timeout::forget) to leak it. Once forgotten, the interval will keep running forever.
27/// This pattern is known as Resource Acquisition Is Initialization (RAII).
28#[derive(Debug)]
29#[must_use = "timeouts cancel on drop; either call `forget` or `drop` explicitly"]
30pub struct Timeout {
31    id: Option<JsValue>,
32    closure: Option<Closure<dyn FnMut()>>,
33}
34
35impl Drop for Timeout {
36    /// Disposes of the timeout, dually cancelling this timeout by calling
37    /// `clearTimeout` directly.
38    fn drop(&mut self) {
39        if let Some(id) = self.id.take() {
40            clear_timeout(id);
41        }
42    }
43}
44
45impl Timeout {
46    /// Schedule a timeout to invoke `callback` in `millis` milliseconds from
47    /// now.
48    ///
49    /// # Example
50    ///
51    /// ```no_run
52    /// use gloo_timers::callback::Timeout;
53    ///
54    /// let timeout = Timeout::new(1_000, move || {
55    ///     // Do something...
56    /// });
57    /// ```
58    pub fn new<F>(millis: u32, callback: F) -> Timeout
59    where
60        F: 'static + FnOnce(),
61    {
62        let closure = Closure::once(callback);
63
64        let id = set_timeout(
65            closure.as_ref().unchecked_ref::<js_sys::Function>(),
66            millis as i32,
67        )
68        .unwrap_throw();
69
70        Timeout {
71            id: Some(id),
72            closure: Some(closure),
73        }
74    }
75
76    /// Forgets this resource without clearing the timeout.
77    ///
78    /// Returns the identifier returned by the original `setTimeout` call, and
79    /// therefore you can still cancel the timeout by calling `clearTimeout`
80    /// directly (perhaps via `web_sys::clear_timeout_with_handle`).
81    ///
82    /// # Example
83    ///
84    /// ```no_run
85    /// use gloo_timers::callback::Timeout;
86    ///
87    /// // We definitely want to do stuff, and aren't going to ever cancel this
88    /// // timeout.
89    /// Timeout::new(1_000, || {
90    ///     // Do stuff...
91    /// }).forget();
92    /// ```
93    pub fn forget(mut self) -> JsValue {
94        let id = self.id.take().unwrap_throw();
95        self.closure.take().unwrap_throw().forget();
96        id
97    }
98
99    /// Cancel this timeout so that the callback is not invoked after the time
100    /// is up.
101    ///
102    /// The scheduled callback is returned.
103    ///
104    /// # Example
105    ///
106    /// ```no_run
107    /// use gloo_timers::callback::Timeout;
108    ///
109    /// let timeout = Timeout::new(1_000, || {
110    ///     // Do stuff...
111    /// });
112    ///
113    /// // If actually we didn't want to set a timer, then cancel it.
114    /// if nevermind() {
115    ///     timeout.cancel();
116    /// }
117    /// # fn nevermind() -> bool { true }
118    /// ```
119    pub fn cancel(mut self) -> Closure<dyn FnMut()> {
120        self.closure.take().unwrap_throw()
121    }
122}
123
124/// A scheduled interval.
125///
126/// See `Interval::new` for scheduling new intervals.
127///
128/// Once scheduled, you can [`drop`] the [`Interval`] to clear it or [`forget`](Interval::forget) to leak it. Once forgotten, the interval will keep running forever.
129/// This pattern is known as Resource Acquisition Is Initialization (RAII).
130#[derive(Debug)]
131#[must_use = "intervals cancel on drop; either call `forget` or `drop` explicitly"]
132pub struct Interval {
133    id: Option<JsValue>,
134    closure: Option<Closure<dyn FnMut()>>,
135}
136
137impl Drop for Interval {
138    /// Disposes of the interval, dually cancelling this interval by calling
139    /// `clearInterval` directly.
140    fn drop(&mut self) {
141        if let Some(id) = self.id.take() {
142            clear_interval(id);
143        }
144    }
145}
146
147impl Interval {
148    /// Schedule an interval to invoke `callback` every `millis` milliseconds.
149    ///
150    /// # Example
151    ///
152    /// ```no_run
153    /// use gloo_timers::callback::Interval;
154    ///
155    /// let interval = Interval::new(1_000, move || {
156    ///     // Do something...
157    /// });
158    /// ```
159    pub fn new<F>(millis: u32, callback: F) -> Interval
160    where
161        F: 'static + FnMut(),
162    {
163        let closure = Closure::wrap(Box::new(callback) as Box<dyn FnMut()>);
164
165        let id = set_interval(
166            closure.as_ref().unchecked_ref::<js_sys::Function>(),
167            millis as i32,
168        )
169        .unwrap_throw();
170
171        Interval {
172            id: Some(id),
173            closure: Some(closure),
174        }
175    }
176
177    /// Forget this resource without clearing the interval.
178    ///
179    /// Returns the identifier returned by the original `setInterval` call, and
180    /// therefore you can still cancel the interval by calling `clearInterval`
181    /// directly (perhaps via `web_sys::clear_interval_with_handle`).
182    ///
183    /// # Example
184    ///
185    /// ```no_run
186    /// use gloo_timers::callback::Interval;
187    ///
188    /// // We want to do stuff every second, indefinitely.
189    /// Interval::new(1_000, || {
190    ///     // Do stuff...
191    /// }).forget();
192    /// ```
193    pub fn forget(mut self) -> JsValue {
194        let id = self.id.take().unwrap_throw();
195        self.closure.take().unwrap_throw().forget();
196        id
197    }
198
199    /// Cancel this interval so that the callback is no longer periodically
200    /// invoked.
201    ///
202    /// The scheduled callback is returned.
203    ///
204    /// # Example
205    ///
206    /// ```no_run
207    /// use gloo_timers::callback::Interval;
208    ///
209    /// let interval = Interval::new(1_000, || {
210    ///     // Do stuff...
211    /// });
212    ///
213    /// // If we don't want this interval to run anymore, then cancel it.
214    /// if nevermind() {
215    ///     interval.cancel();
216    /// }
217    /// # fn nevermind() -> bool { true }
218    /// ```
219    pub fn cancel(mut self) -> Closure<dyn FnMut()> {
220        self.closure.take().unwrap_throw()
221    }
222}