Skip to content

Commit a845f8d

Browse files
committed
x-bow: add for_each_async
1 parent 0cf9384 commit a845f8d

File tree

2 files changed

+95
-1
lines changed

2 files changed

+95
-1
lines changed

x-bow/src/path_ext.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
pub mod bind_for_each;
22
pub mod for_each;
3+
pub mod for_each_async;
34
pub mod signal_stream;
45

56
use std::cell::{Ref, RefMut};
67

7-
use futures_core::Stream;
8+
use futures_core::{Future, Stream};
89

910
use crate::{
1011
borrow_mut_guard::BorrowMutGuard, until_change::UntilChange, Path, ReferencePath, Trackable,
@@ -286,6 +287,41 @@ pub trait PathExt: Path {
286287
for_each::ForEach::new(self, self.until_change(), closure)
287288
}
288289

290+
/// Execute the given async function (a function returning Future)
291+
/// with the data as argument. Repeat every time the data changes.
292+
///
293+
/// If the async function is still executing when the data changes,
294+
/// the execution is canceled (by dropping the Future) so that the new
295+
/// one can start.
296+
///
297+
/// The return future finishes when the data couldn't be accessed
298+
/// (when [borrow_opt][PathExt::borrow_opt] returns None).
299+
///
300+
/// ```
301+
/// # use x_bow::{Path, PathExt};
302+
/// # async fn example(path_to_url: &impl Path<Out = String>) {
303+
/// # struct ui_element;
304+
/// # impl ui_element {
305+
/// # fn set_text(&self, t: &str) {}
306+
/// # }
307+
/// # async fn fetch_content(url: &String) -> String {
308+
/// # todo!();
309+
/// # }
310+
/// // when the URL changes, fetch the content and set the text
311+
/// path_to_url.for_each_async(|url: &String| async move {
312+
/// let content = fetch_content(url).await;
313+
/// ui_element.set_Text(&content);
314+
/// }).await;
315+
/// # }
316+
/// ```
317+
#[must_use = "the returned Future is lazy; await it to make it do work"]
318+
fn for_each_async<F: Future<Output = ()>, C: FnMut(&Self::Out) -> F>(
319+
&self,
320+
closure: C,
321+
) -> for_each_async::ForEachAsync<'_, Self, F, C> {
322+
for_each_async::ForEachAsync::new(self, self.until_change(), closure)
323+
}
324+
289325
/// For making a two-way binding.
290326
///
291327
/// The given `on_change` function is called once in the beginning and

x-bow/src/path_ext/for_each_async.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use std::{future::Future, pin::Pin, task::Poll};
2+
3+
use futures_core::Stream;
4+
5+
use crate::{until_change::UntilChange, Path, PathExt};
6+
7+
pin_project_lite::pin_project! {
8+
/// A [Future]. See [for_each][crate::PathExt::for_each].
9+
pub struct ForEachAsync<'a, P, F: Future<Output = ()>, C: FnMut(&P::Out) -> F>
10+
where
11+
P: Path,
12+
P: ?Sized
13+
{
14+
path: &'a P,
15+
until_change: UntilChange<'a>,
16+
closure: C,
17+
#[pin]
18+
future: Option<F>
19+
}
20+
}
21+
22+
impl<'a, P: Path + ?Sized, F: Future<Output = ()>, C: FnMut(&P::Out) -> F>
23+
ForEachAsync<'a, P, F, C>
24+
{
25+
pub(super) fn new(path: &'a P, until_change: UntilChange<'a>, closure: C) -> Self {
26+
Self {
27+
path,
28+
until_change,
29+
closure,
30+
future: None,
31+
}
32+
}
33+
}
34+
35+
impl<'a, P: Path + ?Sized, F: Future<Output = ()>, C: FnMut(&P::Out) -> F + Unpin> Future
36+
for ForEachAsync<'a, P, F, C>
37+
{
38+
type Output = ();
39+
40+
fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
41+
let mut this = self.project();
42+
let first = !this.until_change.has_been_polled();
43+
if first | Pin::new(this.until_change).poll_next(cx).is_ready() {
44+
if let Some(data) = this.path.borrow_opt().as_deref() {
45+
let fut = (this.closure)(data);
46+
this.future.set(Some(fut));
47+
} else {
48+
return Poll::Ready(());
49+
}
50+
}
51+
if let Some(fut) = this.future.as_mut().as_pin_mut() {
52+
if fut.poll(cx).is_ready() {
53+
this.future.set(None);
54+
}
55+
}
56+
Poll::Pending
57+
}
58+
}

0 commit comments

Comments
 (0)