1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
use std::cell::Cell;
use std::fmt;
use std::future::Future;
use std::ops::Deref;
use std::rc::Rc;
use yew::prelude::*;
use yew::suspense::{Suspension, SuspensionResult};
/// This hook is used to await a future in a suspending context.
///
/// A [Suspension] is created from the passed future and the result of the future
/// is the output of the suspension.
pub struct UseFutureHandle<O> {
inner: UseStateHandle<Option<O>>,
}
impl<O> Deref for UseFutureHandle<O> {
type Target = O;
fn deref(&self) -> &Self::Target {
self.inner.as_ref().unwrap()
}
}
impl<T: fmt::Debug> fmt::Debug for UseFutureHandle<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UseFutureHandle")
.field("value", &format!("{:?}", self.inner))
.finish()
}
}
/// Use the result of an async computation, suspending while waiting.
///
/// Awaits the future returned from the first call to `init_f`, and returns
/// its result in a [`UseFutureHandle`]. Always suspends initially, even if
/// the future is immediately [ready].
///
/// [ready]: std::task::Poll::Ready
///
/// # Example
///
/// ```
/// # use yew::prelude::*;
/// # use yew::suspense::use_future;
/// use gloo::net::http::Request;
///
/// const URL: &str = "https://en.wikipedia.org/w/api.php?\
/// action=query&origin=*&format=json&generator=search&\
/// gsrnamespace=0&gsrlimit=5&gsrsearch='New_England_Patriots'";
///
/// #[function_component]
/// fn WikipediaSearch() -> HtmlResult {
/// let res = use_future(|| async { Request::get(URL).send().await?.text().await })?;
/// let result_html = match *res {
/// Ok(ref res) => html! { res },
/// Err(ref failure) => failure.to_string().into(),
/// };
/// Ok(html! {
/// <p>
/// {"Wikipedia search result: "}
/// {result_html}
/// </p>
/// })
/// }
/// ```
#[hook]
pub fn use_future<F, T, O>(init_f: F) -> SuspensionResult<UseFutureHandle<O>>
where
F: FnOnce() -> T,
T: Future<Output = O> + 'static,
O: 'static,
{
use_future_with((), move |_| init_f())
}
/// Use the result of an async computation with dependencies, suspending while waiting.
///
/// Awaits the future returned from `f` for the latest `deps`. Even if the future is immediately
/// [ready], the hook suspends at least once. If the dependencies
/// change while a future is still pending, the result is never used. This guarantees that your
/// component always sees up-to-date values while it is not suspended.
///
/// [ready]: std::task::Poll::Ready
#[hook]
pub fn use_future_with<F, D, T, O>(deps: D, f: F) -> SuspensionResult<UseFutureHandle<O>>
where
F: FnOnce(Rc<D>) -> T,
T: Future<Output = O> + 'static,
O: 'static,
D: PartialEq + 'static,
{
let output = use_state(|| None);
// We only commit a result if it comes from the latest spawned future. Otherwise, this
// might trigger pointless updates or even override newer state.
let latest_id = use_state(|| Cell::new(0u32));
let suspension = {
let output = output.clone();
use_memo_base(
move |deps| {
let self_id = latest_id.get().wrapping_add(1);
// As long as less than 2**32 futures are in flight wrapping_add is fine
(*latest_id).set(self_id);
let deps = Rc::new(deps);
let task = f(deps.clone());
let suspension = Suspension::from_future(async move {
let result = task.await;
if latest_id.get() == self_id {
output.set(Some(result));
}
});
(suspension, deps)
},
deps,
)
};
if suspension.resumed() {
Ok(UseFutureHandle { inner: output })
} else {
Err((*suspension).clone())
}
}