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 {}