app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit ec575484da02cf9aa3f9c07eacf6e3b2102d64bb
parent 9c9a3c80e25da686f32079ed6bd05177ad8b14fd
Author: triesap <triesap@radroots.dev>
Date:   Mon, 19 Jan 2026 07:09:25 +0000

app-utils: add async iteration helper

- add exe_iter async helper with delay
- provide wasm and native sleep support
- export exe_iter from utils crate
- add unit test for callback counting

Diffstat:
MCargo.lock | 12++++++++++++
MCargo.toml | 2++
Mcrates/utils/Cargo.toml | 6++++++
Acrates/utils/src/async/mod.rs | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/utils/src/lib.rs | 2++
5 files changed, 85 insertions(+), 0 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -715,6 +715,16 @@ dependencies = [ ] [[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] name = "gloo-utils" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1565,7 +1575,9 @@ dependencies = [ name = "radroots-app-utils" version = "0.1.0" dependencies = [ + "futures", "getrandom 0.2.17", + "gloo-timers", "js-sys", "radroots-types", "serde_json", diff --git a/Cargo.toml b/Cargo.toml @@ -21,8 +21,10 @@ leptos = { version = "0.8.5", default-features = false } wasm-bindgen = "=0.2.100" serde = { version = "1", features = ["derive"] } serde_json = "1" +futures = "0.3" getrandom = "0.2" js-sys = "0.3.77" +gloo-timers = "0.3" web-sys = { version = "0.3.77", features = [ "Crypto", "CryptoKey", diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml @@ -15,3 +15,9 @@ getrandom = { workspace = true } js-sys = { workspace = true } web-sys = { workspace = true } serde_json = { workspace = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +gloo-timers = { workspace = true } + +[dev-dependencies] +futures = { workspace = true } diff --git a/crates/utils/src/async/mod.rs b/crates/utils/src/async/mod.rs @@ -0,0 +1,63 @@ +#![forbid(unsafe_code)] + +use crate::error::RadrootsAppUtilsError; +use std::future::Future; +use std::time::Duration; + +pub async fn exe_iter<F, Fut>( + callback: F, + num: usize, + delay_ms: u64, +) -> Result<(), RadrootsAppUtilsError> +where + F: Fn() -> Fut, + Fut: Future<Output = ()>, +{ + if num == 0 { + return Ok(()); + } + for index in 0..num { + callback().await; + if index + 1 < num { + sleep_ms(delay_ms).await?; + } + } + Ok(()) +} + +#[cfg(target_arch = "wasm32")] +async fn sleep_ms(delay_ms: u64) -> Result<(), RadrootsAppUtilsError> { + gloo_timers::future::TimeoutFuture::new(delay_ms as u32).await; + Ok(()) +} + +#[cfg(not(target_arch = "wasm32"))] +async fn sleep_ms(delay_ms: u64) -> Result<(), RadrootsAppUtilsError> { + std::thread::sleep(Duration::from_millis(delay_ms)); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::exe_iter; + use std::sync::{Arc, Mutex}; + + #[test] + fn exe_iter_runs_callback() { + let counter = Arc::new(Mutex::new(0usize)); + let counter_ref = Arc::clone(&counter); + let task = exe_iter( + move || { + let counter_ref = Arc::clone(&counter_ref); + async move { + let mut guard = counter_ref.lock().expect("lock"); + *guard += 1; + } + }, + 3, + 0, + ); + futures::executor::block_on(task).expect("exe_iter"); + assert_eq!(*counter.lock().expect("lock"), 3); + } +} diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs @@ -2,6 +2,7 @@ pub mod error; pub mod errors; +pub mod r#async; pub mod binary; pub mod numbers; pub mod object; @@ -10,6 +11,7 @@ pub mod text; pub mod time; pub mod types; +pub use r#async::exe_iter; pub use binary::{as_array_buffer, RadrootsAppArrayBuffer}; pub use errors::{err_msg, handle_err, throw_err, ERR_PREFIX_APP, ERR_PREFIX_UTILS}; pub use numbers::{num_interval_range, num_str, parse_float, parse_int};