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}