sycamore_futures/
lib.rs

1//! Futures support for reactive scopes.
2
3#![deny(missing_debug_implementations)]
4#![warn(missing_docs)]
5
6mod suspense;
7
8use std::pin::Pin;
9use std::task::{Context, Poll};
10
11use futures::future::abortable;
12use futures::stream::Abortable;
13use futures::Future;
14use pin_project::pin_project;
15use sycamore_reactive::{on_cleanup, use_current_scope, NodeHandle};
16
17pub use self::suspense::*;
18
19/// If running on `wasm32` target, does nothing. Otherwise creates a new `tokio::task::LocalSet`
20/// scope.
21pub async fn provide_executor_scope<U>(fut: impl Future<Output = U>) -> U {
22    #[cfg(all(target_arch = "wasm32", not(sycamore_force_ssr)))]
23    {
24        fut.await
25    }
26    #[cfg(any(not(target_arch = "wasm32"), sycamore_force_ssr))]
27    {
28        let local = tokio::task::LocalSet::new();
29        local.run_until(fut).await
30    }
31}
32
33/// Spawns a `!Send` future.
34///
35/// This will not auto cancel the task if the scope in which it is created is destroyed.
36/// For this purpose, use [`spawn_local_scoped`] instead.
37pub fn spawn_local(fut: impl Future<Output = ()> + 'static) {
38    #[cfg(any(not(target_arch = "wasm32"), sycamore_force_ssr))]
39    tokio::task::spawn_local(fut);
40    #[cfg(all(target_arch = "wasm32", not(sycamore_force_ssr)))]
41    wasm_bindgen_futures::spawn_local(fut);
42}
43
44/// Spawns a `!Send` future on the current scope.
45///
46/// If the scope is destroyed before the future is completed, it is aborted immediately. This
47/// ensures that it is impossible to access any values referencing the scope after they are
48/// destroyed.
49pub fn spawn_local_scoped(fut: impl Future<Output = ()> + 'static) {
50    let scoped = ScopedFuture::new_in_current_scope(fut);
51    spawn_local(scoped);
52}
53
54/// A wrapper that runs the future on the current scope.
55#[pin_project]
56struct ScopedFuture<T> {
57    #[pin]
58    task: Abortable<T>,
59    scope: NodeHandle,
60}
61
62impl<T: Future> Future for ScopedFuture<T> {
63    type Output = ();
64
65    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
66        let this = self.project();
67        this.scope.run_in(move || this.task.poll(cx).map(|_| ()))
68    }
69}
70
71impl<T: Future> ScopedFuture<T> {
72    pub fn new_in_current_scope(f: T) -> Self {
73        let (abortable, handle) = abortable(f);
74        on_cleanup(move || handle.abort());
75
76        let scope = use_current_scope();
77
78        Self {
79            task: abortable,
80            scope,
81        }
82    }
83}