This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew/suspense/
suspension.rs

1use std::cell::RefCell;
2use std::future::Future;
3use std::pin::Pin;
4use std::rc::Rc;
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::task::{Context, Poll};
7
8use thiserror::Error;
9
10use crate::platform::spawn_local;
11use crate::Callback;
12
13thread_local! {
14    static SUSPENSION_ID: RefCell<usize> = RefCell::default();
15}
16
17/// A Suspension.
18///
19/// This type can be sent back as an `Err(_)` to suspend a component until the underlying task
20/// completes.
21#[derive(Error, Debug, Clone)]
22#[error("suspend component rendering")]
23pub struct Suspension {
24    id: usize,
25    listeners: Rc<RefCell<Vec<Callback<Self>>>>,
26
27    resumed: Rc<AtomicBool>,
28}
29
30impl PartialEq for Suspension {
31    fn eq(&self, rhs: &Self) -> bool {
32        self.id == rhs.id
33    }
34}
35
36impl Suspension {
37    /// Creates a Suspension.
38    pub fn new() -> (Self, SuspensionHandle) {
39        let id = SUSPENSION_ID.with(|m| {
40            let mut m = m.borrow_mut();
41            *m += 1;
42
43            *m
44        });
45
46        let self_ = Suspension {
47            id,
48            listeners: Rc::default(),
49            resumed: Rc::default(),
50        };
51
52        (self_.clone(), SuspensionHandle { inner: self_ })
53    }
54
55    /// Returns `true` if the current suspension is already resumed.
56    pub fn resumed(&self) -> bool {
57        self.resumed.load(Ordering::Relaxed)
58    }
59
60    /// Creates a Suspension that resumes when the [`Future`] resolves.
61    pub fn from_future(f: impl Future<Output = ()> + 'static) -> Self {
62        let (self_, handle) = Self::new();
63
64        spawn_local(async move {
65            f.await;
66            handle.resume();
67        });
68
69        self_
70    }
71
72    /// Listens to a suspension and get notified when it resumes.
73    pub(crate) fn listen(&self, cb: Callback<Self>) {
74        if self.resumed() {
75            cb.emit(self.clone());
76            return;
77        }
78
79        let mut listeners = self.listeners.borrow_mut();
80
81        listeners.push(cb);
82    }
83
84    fn resume_by_ref(&self) {
85        // The component can resume rendering by returning a non-suspended result after a state is
86        // updated, so we always need to check here.
87        if !self.resumed() {
88            self.resumed.store(true, Ordering::Relaxed);
89            let listeners = self.listeners.borrow();
90
91            for listener in listeners.iter() {
92                listener.emit(self.clone());
93            }
94        }
95    }
96}
97
98impl Future for Suspension {
99    type Output = ();
100
101    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
102        if self.resumed() {
103            return Poll::Ready(());
104        }
105
106        let waker = cx.waker().clone();
107        self.listen(Callback::from(move |_| {
108            waker.wake_by_ref();
109        }));
110
111        Poll::Pending
112    }
113}
114
115/// A Suspension Result.
116pub type SuspensionResult<T> = std::result::Result<T, Suspension>;
117
118/// A Suspension Handle.
119///
120/// This type is used to control the corresponding [`Suspension`].
121///
122/// When the current struct is dropped or `resume` is called, it will resume rendering of current
123/// component.
124#[derive(Debug, PartialEq)]
125pub struct SuspensionHandle {
126    inner: Suspension,
127}
128
129impl SuspensionHandle {
130    /// Resumes component rendering.
131    pub fn resume(self) {
132        self.inner.resume_by_ref();
133    }
134}
135
136impl Drop for SuspensionHandle {
137    fn drop(&mut self) {
138        self.inner.resume_by_ref();
139    }
140}