Skip to main content

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