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