gloo-timers-0.3.0/.cargo_vcs_info.json0000644000000001530000000000100132740ustar { "git": { "sha1": "f3bb1d1a1c33dbcccaf26446aca7d0cea8e9d574" }, "path_in_vcs": "crates/timers" }gloo-timers-0.3.0/Cargo.toml0000644000000026160000000000100113000ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.64" name = "gloo-timers" version = "0.3.0" authors = ["Rust and WebAssembly Working Group"] description = "Convenience crate for working with JavaScript timers" homepage = "https://github.com/rustwasm/gloo" readme = "README.md" categories = [ "api-bindings", "asynchronous", "wasm", ] license = "MIT OR Apache-2.0" repository = "https://github.com/rustwasm/gloo/tree/master/crates/timers" [package.metadata.docs.rs] features = ["futures"] [dependencies.futures-channel] version = "0.3" optional = true [dependencies.futures-core] version = "0.3" optional = true [dependencies.js-sys] version = "0.3.31" [dependencies.wasm-bindgen] version = "0.2" [dev-dependencies.futures-util] version = "0.3" [dev-dependencies.wasm-bindgen-futures] version = "0.4.4" [dev-dependencies.wasm-bindgen-test] version = "0.3.4" [features] default = [] futures = [ "futures-core", "futures-channel", ] gloo-timers-0.3.0/Cargo.toml.orig000064400000000000000000000014601046102023000147550ustar 00000000000000[package] name = "gloo-timers" description = "Convenience crate for working with JavaScript timers" version = "0.3.0" authors = ["Rust and WebAssembly Working Group"] edition = "2021" license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/rustwasm/gloo/tree/master/crates/timers" homepage = "https://github.com/rustwasm/gloo" categories = ["api-bindings", "asynchronous", "wasm"] rust-version = "1.64" [package.metadata.docs.rs] features = ["futures"] [dependencies] wasm-bindgen = "0.2" js-sys = "0.3.31" futures-core = { version = "0.3", optional = true } futures-channel = { version = "0.3", optional = true } [features] default = [] futures = ["futures-core", "futures-channel"] [dev-dependencies] wasm-bindgen-futures = "0.4.4" wasm-bindgen-test = "0.3.4" futures-util = "0.3" gloo-timers-0.3.0/README.md000064400000000000000000000031611046102023000133450ustar 00000000000000

gloo-timers

Crates.io version Download docs.rs docs

API Docs | Contributing | Chat

Built with 🦀🕸 by The Rust and WebAssembly Working Group
Working with timers on the Web: `setTimeout` and `setInterval`. These APIs come in two flavors: 1. a callback style (that more directly mimics the JavaScript APIs), and 2. a `Future`s and `Stream`s API. ### Timeouts Timeouts fire once after a period of time (measured in milliseconds). #### Timeouts with a Callback Function ```rust use gloo_timers::callback::Timeout; let timeout = Timeout::new(1_000, move || { // Do something after the one second timeout is up! }); // Since we don't plan on cancelling the timeout, call `forget`. timeout.forget(); ``` #### Timeouts as `Future`s With the `futures` feature enabled, a `future` module containing futures-based timers is exposed. gloo-timers-0.3.0/src/callback.rs000064400000000000000000000143601046102023000147620ustar 00000000000000//! Callback-style timer APIs. use js_sys::Function; use wasm_bindgen::prelude::*; use wasm_bindgen::{JsCast, JsValue}; #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_name = "setTimeout", catch)] fn set_timeout(handler: &Function, timeout: i32) -> Result; #[wasm_bindgen(js_name = "setInterval", catch)] fn set_interval(handler: &Function, timeout: i32) -> Result; #[wasm_bindgen(js_name = "clearTimeout")] fn clear_timeout(handle: JsValue) -> JsValue; #[wasm_bindgen(js_name = "clearInterval")] fn clear_interval(handle: JsValue) -> JsValue; } /// A scheduled timeout. /// /// See `Timeout::new` for scheduling new timeouts. /// /// 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. /// This pattern is known as Resource Acquisition Is Initialization (RAII). #[derive(Debug)] #[must_use = "timeouts cancel on drop; either call `forget` or `drop` explicitly"] pub struct Timeout { id: Option, closure: Option>, } impl Drop for Timeout { /// Disposes of the timeout, dually cancelling this timeout by calling /// `clearTimeout` directly. fn drop(&mut self) { if let Some(id) = self.id.take() { clear_timeout(id); } } } impl Timeout { /// Schedule a timeout to invoke `callback` in `millis` milliseconds from /// now. /// /// # Example /// /// ```no_run /// use gloo_timers::callback::Timeout; /// /// let timeout = Timeout::new(1_000, move || { /// // Do something... /// }); /// ``` pub fn new(millis: u32, callback: F) -> Timeout where F: 'static + FnOnce(), { let closure = Closure::once(callback); let id = set_timeout( closure.as_ref().unchecked_ref::(), millis as i32, ) .unwrap_throw(); Timeout { id: Some(id), closure: Some(closure), } } /// Forgets this resource without clearing the timeout. /// /// Returns the identifier returned by the original `setTimeout` call, and /// therefore you can still cancel the timeout by calling `clearTimeout` /// directly (perhaps via `web_sys::clear_timeout_with_handle`). /// /// # Example /// /// ```no_run /// use gloo_timers::callback::Timeout; /// /// // We definitely want to do stuff, and aren't going to ever cancel this /// // timeout. /// Timeout::new(1_000, || { /// // Do stuff... /// }).forget(); /// ``` pub fn forget(mut self) -> JsValue { let id = self.id.take().unwrap_throw(); self.closure.take().unwrap_throw().forget(); id } /// Cancel this timeout so that the callback is not invoked after the time /// is up. /// /// The scheduled callback is returned. /// /// # Example /// /// ```no_run /// use gloo_timers::callback::Timeout; /// /// let timeout = Timeout::new(1_000, || { /// // Do stuff... /// }); /// /// // If actually we didn't want to set a timer, then cancel it. /// if nevermind() { /// timeout.cancel(); /// } /// # fn nevermind() -> bool { true } /// ``` pub fn cancel(mut self) -> Closure { self.closure.take().unwrap_throw() } } /// A scheduled interval. /// /// See `Interval::new` for scheduling new intervals. /// /// 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. /// This pattern is known as Resource Acquisition Is Initialization (RAII). #[derive(Debug)] #[must_use = "intervals cancel on drop; either call `forget` or `drop` explicitly"] pub struct Interval { id: Option, closure: Option>, } impl Drop for Interval { /// Disposes of the interval, dually cancelling this interval by calling /// `clearInterval` directly. fn drop(&mut self) { if let Some(id) = self.id.take() { clear_interval(id); } } } impl Interval { /// Schedule an interval to invoke `callback` every `millis` milliseconds. /// /// # Example /// /// ```no_run /// use gloo_timers::callback::Interval; /// /// let interval = Interval::new(1_000, move || { /// // Do something... /// }); /// ``` pub fn new(millis: u32, callback: F) -> Interval where F: 'static + FnMut(), { let closure = Closure::wrap(Box::new(callback) as Box); let id = set_interval( closure.as_ref().unchecked_ref::(), millis as i32, ) .unwrap_throw(); Interval { id: Some(id), closure: Some(closure), } } /// Forget this resource without clearing the interval. /// /// Returns the identifier returned by the original `setInterval` call, and /// therefore you can still cancel the interval by calling `clearInterval` /// directly (perhaps via `web_sys::clear_interval_with_handle`). /// /// # Example /// /// ```no_run /// use gloo_timers::callback::Interval; /// /// // We want to do stuff every second, indefinitely. /// Interval::new(1_000, || { /// // Do stuff... /// }).forget(); /// ``` pub fn forget(mut self) -> JsValue { let id = self.id.take().unwrap_throw(); self.closure.take().unwrap_throw().forget(); id } /// Cancel this interval so that the callback is no longer periodically /// invoked. /// /// The scheduled callback is returned. /// /// # Example /// /// ```no_run /// use gloo_timers::callback::Interval; /// /// let interval = Interval::new(1_000, || { /// // Do stuff... /// }); /// /// // If we don't want this interval to run anymore, then cancel it. /// if nevermind() { /// interval.cancel(); /// } /// # fn nevermind() -> bool { true } /// ``` pub fn cancel(mut self) -> Closure { self.closure.take().unwrap_throw() } } gloo-timers-0.3.0/src/future.rs000064400000000000000000000112131046102023000145320ustar 00000000000000//! `Future`- and `Stream`-backed timers APIs. use crate::callback::{Interval, Timeout}; use futures_channel::{mpsc, oneshot}; use futures_core::stream::Stream; use std::convert::TryFrom; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; use std::time::Duration; use wasm_bindgen::prelude::*; /// A scheduled timeout as a `Future`. /// /// See `TimeoutFuture::new` for scheduling new timeouts. /// /// Once scheduled, if you change your mind and don't want the timeout to fire, /// you can `drop` the future. /// /// A timeout future will never resolve to `Err`. Its only failure mode is when /// the timeout is so long that it is effectively infinite and never fires. /// /// # Example /// /// ```no_run /// use gloo_timers::future::TimeoutFuture; /// use futures_util::future::{select, Either}; /// use wasm_bindgen_futures::spawn_local; /// /// spawn_local(async { /// match select(TimeoutFuture::new(1_000), TimeoutFuture::new(2_000)).await { /// Either::Left((val, b)) => { /// // Drop the `2_000` ms timeout to cancel its timeout. /// drop(b); /// } /// Either::Right((a, val)) => { /// panic!("the `1_000` ms timeout should have won this race"); /// } /// } /// }); /// ``` #[derive(Debug)] #[must_use = "futures do nothing unless polled or spawned"] pub struct TimeoutFuture { _inner: Timeout, rx: oneshot::Receiver<()>, } impl TimeoutFuture { /// Create a new timeout future. /// /// Remember that futures do nothing unless polled or spawned, so either /// pass this future to `wasm_bindgen_futures::spawn_local` or use it inside /// another future. /// /// # Example /// /// ```no_run /// use gloo_timers::future::TimeoutFuture; /// use wasm_bindgen_futures::spawn_local; /// /// spawn_local(async { /// TimeoutFuture::new(1_000).await; /// // Do stuff after one second... /// }); /// ``` pub fn new(millis: u32) -> TimeoutFuture { let (tx, rx) = oneshot::channel(); let inner = Timeout::new(millis, move || { // if the receiver was dropped we do nothing. tx.send(()).unwrap_throw(); }); TimeoutFuture { _inner: inner, rx } } } /// Waits until the specified duration has elapsed. /// /// # Panics /// /// This function will panic if the specified [`Duration`] cannot be casted into a u32 in /// milliseconds. /// /// # Example /// /// ```compile_fail /// use std::time::Duration; /// use gloo_timers::future::sleep; /// /// sleep(Duration::from_secs(1)).await; /// ``` pub fn sleep(dur: Duration) -> TimeoutFuture { let millis = u32::try_from(dur.as_millis()) .expect_throw("failed to cast the duration into a u32 with Duration::as_millis."); TimeoutFuture::new(millis) } impl Future for TimeoutFuture { type Output = (); fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { Future::poll(Pin::new(&mut self.rx), cx).map(|t| t.unwrap_throw()) } } /// A scheduled interval as a `Stream`. /// /// See `IntervalStream::new` for scheduling new intervals. /// /// Once scheduled, if you want to stop the interval from continuing to fire, /// you can `drop` the stream. /// /// An interval stream will never resolve to `Err`. #[derive(Debug)] #[must_use = "streams do nothing unless polled or spawned"] pub struct IntervalStream { receiver: mpsc::UnboundedReceiver<()>, _inner: Interval, } impl IntervalStream { /// Create a new interval stream. /// /// Remember that streams do nothing unless polled or spawned, so either /// spawn this stream via `wasm_bindgen_futures::spawn_local` or use it inside /// another stream or future. /// /// # Example /// /// ```compile_fail /// use futures_util::stream::StreamExt; /// use gloo_timers::future::IntervalStream; /// use wasm_bindgen_futures::spawn_local; /// /// spawn_local(async { /// IntervalStream::new(1_000).for_each(|_| { /// // Do stuff every one second... /// }).await; /// }); /// ``` pub fn new(millis: u32) -> IntervalStream { let (sender, receiver) = mpsc::unbounded(); let inner = Interval::new(millis, move || { // if the receiver was dropped we do nothing. sender.unbounded_send(()).unwrap_throw(); }); IntervalStream { receiver, _inner: inner, } } } impl Stream for IntervalStream { type Item = (); fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { Stream::poll_next(Pin::new(&mut self.receiver), cx) } } gloo-timers-0.3.0/src/lib.rs000064400000000000000000000026231046102023000137730ustar 00000000000000/*! Working with timers on the Web: `setTimeout` and `setInterval`. These APIs come in two flavors: 1. a callback style (that more directly mimics the JavaScript APIs), and 2. a `Future`s and `Stream`s API. ## Timeouts Timeouts fire once after a period of time (measured in milliseconds). ### Timeouts with a Callback Function ```no_run use gloo_timers::callback::Timeout; let timeout = Timeout::new(1_000, move || { // Do something after the one second timeout is up! }); // Since we don't plan on cancelling the timeout, call `forget`. timeout.forget(); ``` ### Timeouts as `Future`s With the `futures` feature enabled, a `future` module containing futures-based timers is exposed. */ #![cfg_attr(feature = "futures", doc = "```no_run")] #![cfg_attr(not(feature = "futures"), doc = "```ignore")] /*! use gloo_timers::future::TimeoutFuture; use wasm_bindgen_futures::spawn_local; // Spawn the `timeout` future on the local thread. If we just dropped it, then // the timeout would be cancelled with `clearTimeout`. spawn_local(async { TimeoutFuture::new(1_000).await; // Do something here after the one second timeout is up! }); ``` ## Intervals Intervals fire repeatedly every *n* milliseconds. ### Intervals with a Callback Function TODO ### Intervals as `Stream`s TODO */ #![deny(missing_docs, missing_debug_implementations)] pub mod callback; #[cfg(feature = "futures")] pub mod future; gloo-timers-0.3.0/tests/node.rs000064400000000000000000000053131046102023000145240ustar 00000000000000#![cfg(all(target_family = "wasm", feature = "futures"))] use futures_channel::{mpsc, oneshot}; use futures_util::{ future::{select, Either, FutureExt}, stream::StreamExt, }; use gloo_timers::{ callback::{Interval, Timeout}, future::{sleep, IntervalStream, TimeoutFuture}, }; use std::cell::Cell; use std::rc::Rc; use std::time::Duration; use wasm_bindgen_test::*; #[wasm_bindgen_test] async fn timeout() { let (sender, receiver) = oneshot::channel(); Timeout::new(1, || sender.send(()).unwrap()).forget(); receiver.await.unwrap(); } #[wasm_bindgen_test] async fn timeout_cancel() { let cell = Rc::new(Cell::new(false)); let t = Timeout::new(1, { let cell = cell.clone(); move || { cell.set(true); panic!("should have been cancelled"); } }); t.cancel(); let (sender, receiver) = oneshot::channel(); Timeout::new(2, move || { sender.send(()).unwrap(); assert_eq!(cell.get(), false); }) .forget(); receiver.await.unwrap(); } #[wasm_bindgen_test] async fn timeout_future() { TimeoutFuture::new(1).await; } #[wasm_bindgen_test] async fn timeout_future_cancel() { let cell = Rc::new(Cell::new(false)); let a = TimeoutFuture::new(1).map({ let cell = cell.clone(); move |_| { assert_eq!(cell.get(), false); 1 } }); let b = TimeoutFuture::new(2).map({ let cell = cell.clone(); move |_| { cell.set(true); 2u32 } }); let (who, other) = match select(a, b).await { Either::Left(x) => x, Either::Right(_) => panic!("Timer for 2 ms finished before timer for 1 ms"), }; assert_eq!(who, 1); // Drop `b` so that its timer is canceled. drop(other); TimeoutFuture::new(3).await; // We should never have fired `b`'s timer. assert_eq!(cell.get(), false); } #[wasm_bindgen_test] async fn interval() { let (mut sender, receiver) = mpsc::channel(1); let i = Interval::new(1, move || { if !sender.is_closed() { sender.try_send(()).unwrap() } }); let results: Vec<_> = receiver.take(5).collect().await; drop(i); assert_eq!(results.len(), 5); } #[wasm_bindgen_test] async fn interval_cancel() { let i = Interval::new(10, move || { panic!("This should never be called"); }); i.cancel(); // This keeps us live for long enough that if any erroneous Interval callbacks fired, we'll have seen them. sleep(Duration::from_millis(100)).await; } #[wasm_bindgen_test] async fn interval_stream() { let results: Vec<_> = IntervalStream::new(1).take(5).collect().await; assert_eq!(results.len(), 5); } gloo-timers-0.3.0/tests/web.rs000064400000000000000000000053721046102023000143610ustar 00000000000000#![cfg(all(target_family = "wasm", feature = "futures"))] use futures_channel::{mpsc, oneshot}; use futures_util::{ future::{select, Either, FutureExt}, stream::StreamExt, }; use gloo_timers::{ callback::{Interval, Timeout}, future::{sleep, IntervalStream, TimeoutFuture}, }; use std::cell::Cell; use std::rc::Rc; use std::time::Duration; use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); #[wasm_bindgen_test] async fn timeout() { let (sender, receiver) = oneshot::channel(); Timeout::new(1, || sender.send(()).unwrap()).forget(); receiver.await.unwrap(); } #[wasm_bindgen_test] async fn timeout_cancel() { let cell = Rc::new(Cell::new(false)); let t = Timeout::new(1, { let cell = cell.clone(); move || { cell.set(true); panic!("should have been cancelled"); } }); t.cancel(); let (sender, receiver) = oneshot::channel(); Timeout::new(2, move || { sender.send(()).unwrap(); assert_eq!(cell.get(), false); }) .forget(); receiver.await.unwrap(); } #[wasm_bindgen_test] async fn timeout_future() { TimeoutFuture::new(1).await; } #[wasm_bindgen_test] async fn timeout_future_cancel() { let cell = Rc::new(Cell::new(false)); let a = TimeoutFuture::new(1).map({ let cell = cell.clone(); move |_| { assert_eq!(cell.get(), false); 1 } }); let b = TimeoutFuture::new(2).map({ let cell = cell.clone(); move |_| { cell.set(true); 2u32 } }); let (who, other) = match select(a, b).await { Either::Left(x) => x, Either::Right(_) => panic!("Timer for 2 ms finished before timer for 1 ms"), }; assert_eq!(who, 1); // Drop `b` so that its timer is canceled. drop(other); TimeoutFuture::new(3).await; // We should never have fired `b`'s timer. assert_eq!(cell.get(), false); } #[wasm_bindgen_test] async fn interval() { let (mut sender, receiver) = mpsc::channel(1); let i = Interval::new(1, move || { if !sender.is_closed() { sender.try_send(()).unwrap() } }); let results: Vec<_> = receiver.take(5).collect().await; drop(i); assert_eq!(results.len(), 5); } #[wasm_bindgen_test] async fn interval_cancel() { let i = Interval::new(10, move || { panic!("This should never be called"); }); i.cancel(); // This keeps us live for long enough that if any erroneous Interval callbacks fired, we'll have seen them. sleep(Duration::from_millis(100)).await; } #[wasm_bindgen_test] async fn interval_stream() { let results: Vec<_> = IntervalStream::new(1).take(5).collect().await; assert_eq!(results.len(), 5); }