async_iter_ext/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use combinator::{filter::AsyncFilter, map::AsyncMap};
4
5pub mod combinator;
6pub mod iter;
7mod option;
8mod result;
9
10pub use iter::AsyncIterator;
11pub use option::AsyncOptionTools;
12pub use result::AsyncResultTools;
13
14use crate::iter::process_result::ProcessResults;
15
16pub trait AsyncIterTools: AsyncIterator {
17    /// Calls an async closure on each element of an iterator
18    ///
19    /// This is equivalent to using a for loop on the iterator, although break and continue are not possible from a closure.
20    ///
21    /// ---
22    ///
23    /// # Examples
24    ///
25    /// Basic usage:
26    ///
27    /// ```rust
28    /// use std::time::Duration;
29    ///
30    /// use async_iter_ext::AsyncIterTools;
31    /// use async_std::task;
32    ///
33    /// task::block_on(async {
34    ///   let items = [1, 2, 3, 4];
35    ///   items
36    ///     .iter()
37    ///     .for_each_async(|item| {
38    ///       async move {
39    ///         // Simulate some async work
40    ///         task::sleep(Duration::from_millis(100)).await;
41    ///         println!("Processing {}", item);
42    ///       }
43    ///     })
44    ///     .await;
45    /// });
46    /// ```
47    fn for_each_async<F, Fut>(mut self, f: F) -> impl Future<Output = ()>
48    where
49        Self: Sized,
50        F: Fn(Self::Item) -> Fut,
51        Fut: Future<Output = ()>,
52    {
53        async move {
54            while let Some(item) = self.next_async().await {
55                f(item).await;
56            }
57        }
58    }
59
60    /// Applies an async closure to each item of the iterator, returning a new iterator
61    /// of the results of each async computation.
62    ///
63    /// This is similar to the standard `Iterator::map` method, but works with asynchronous
64    /// closures that return `Future`s.
65    ///
66    /// ---
67    ///
68    /// # Examples
69    ///
70    /// ```rust
71    /// use std::time::Duration;
72    ///
73    /// use async_iter_ext::{AsyncIterTools, AsyncIterator};
74    /// use async_std::task;
75    ///
76    /// let multiplied_by_two = task::block_on(async {
77    ///   let items = [1, 2, 3, 4];
78    ///   items
79    ///     .iter()
80    ///     .map_async(|item| {
81    ///       async move {
82    ///         // Simulate async transformation
83    ///         task::sleep(Duration::from_millis(100)).await;
84    ///         item * 2
85    ///       }
86    ///     })
87    ///     .async_collect::<Vec<_>>()
88    ///     .await
89    /// });
90    /// ```
91    fn map_async<B, F, Fut>(self, f: F) -> AsyncMap<Self, F>
92    where
93        Self: Sized,
94        F: Fn(Self::Item) -> Fut,
95        Fut: Future<Output = B>,
96    {
97        AsyncMap { iter: self, f }
98    }
99
100    /// Filters the items of an iterator using an asynchronous predicate.
101    ///
102    /// This works like the standard `Iterator::filter`, but allows the predicate
103    /// to be asynchronous by returning a `Future<Output = bool>`.
104    ///
105    /// ---
106    ///
107    /// > ⚠️ Warning: The item type must implement `Clone` because filtering requires that the item is cloned when the predicate is run.
108    ///
109    /// ---
110    ///
111    /// # Examples
112    ///
113    /// ```rust
114    /// use async_iter_ext::{AsyncIterTools, AsyncIterator};
115    /// use async_std::task;
116    ///
117    /// task::block_on(async {
118    ///   let items = [1, 2, 3, 4, 5, 6];
119    ///   let filtered = items
120    ///     .iter()
121    ///     .filter_async(|item| {
122    ///       async move {
123    ///         // Keep even numbers only
124    ///         item % 2 == 0
125    ///       }
126    ///     })
127    ///     .async_collect::<Vec<_>>();
128    /// });
129    /// ```
130    fn filter_async<F, Fut>(self, f: F) -> AsyncFilter<Self, F>
131    where
132        Self: Sized,
133        F: Fn(Self::Item) -> Fut,
134        Fut: Future<Output = bool>,
135        Self::Item: Clone,
136    {
137        AsyncFilter { iter: self, f }
138    }
139
140    /// Consumes the async iterator and returns a `ProcessResults` future that collects
141    /// successes and errors based on a specified strategy.
142    ///
143    /// This is a convenience method for turning an `AsyncIterator<Item = Result<T, E>>`
144    /// into a `ProcessResults`, which is a future that resolves to a
145    /// `ProcessResultsContainer<T, E>`.
146    ///
147    /// By default, the `ProcessResultsStrategy::Partition` strategy is used,
148    /// which means all items will be processed and separated into successes and errors.
149    /// You can change the strategy using `.with_process_strategy(...)`.
150    ///
151    /// # Type Parameters
152    /// - `T`: The success type inside the `Result`.
153    /// - `E`: The error type inside the `Result`.
154    ///
155    /// # Returns
156    /// A `ProcessResults<Self, T, E>` future that resolves to `ProcessResultsContainer<T, E>`.
157    ///
158    /// # Examples
159    ///
160    /// ```rust
161    /// use async_iter_ext::{AsyncIterTools, AsyncIterator, iter::process_result::ProcessResultsStrategy};
162    /// use async_std::task;
163    ///
164    /// task::block_on(async {
165    ///   // Partition strategy: collect all successes and errors
166    ///   let results = vec![Ok(1), Err("err1"), Ok(2), Err("err2")]
167    ///     .into_iter()
168    ///     .process_results::<i32, &str>()
169    ///     .await;
170    ///   assert_eq!(results.successes(), &vec![1, 2]);
171    ///   assert_eq!(results.errors(), &vec!["err1", "err2"]);
172    ///
173    ///   // Convert to result and unwrap error. OBS: Only gets the first error
174    ///   let first_result_err = results.into_result().unwrap_err();
175    ///   assert_eq!(first_result_err, "err1");
176    ///
177    ///   // Partition strategy: collect all successes
178    ///   let results = vec![Ok(1), Ok(2)].into_iter().process_results::<i32, &str>().await;
179    ///   assert_eq!(results.successes(), &vec![1, 2]);
180    ///
181    ///   // Convert to result and unwrap
182    ///   let successes = results.into_result().unwrap();
183    ///   assert_eq!(successes, vec![1, 2]);
184    ///
185    ///   // BreakOnError strategy: stop at the first error
186    ///   let results = vec![Ok(1), Err("early"), Ok(2)]
187    ///     .into_iter()
188    ///     .process_results::<i32, &str>()
189    ///     .with_process_strategy(ProcessResultsStrategy::BreakOnError)
190    ///     .await;
191    ///   assert_eq!(results.successes(), &vec![]); // did not continue after first error
192    ///   assert_eq!(results.errors(), &vec!["early"]);
193    /// });
194    /// ```
195    fn process_results<T, E>(self) -> ProcessResults<Self, T, E>
196    where
197        Self: Sized + AsyncIterator<Item = Result<T, E>>,
198    {
199        ProcessResults::new(self)
200    }
201}
202
203impl<T> AsyncIterTools for T where T: AsyncIterator + ?Sized {}