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.
49#[cfg_attr(debug_assertions, track_caller)]
50pub fn spawn_local_scoped(fut: impl Future<Output = ()> + 'static) {
51    let scoped = ScopedFuture::new_in_current_scope(fut);
52    spawn_local(scoped);
53}
54
55/// A wrapper that runs the future on the current scope.
56#[pin_project]
57struct ScopedFuture<T> {
58    #[pin]
59    task: Abortable<T>,
60    scope: NodeHandle,
61}
62
63impl<T: Future> Future for ScopedFuture<T> {
64    type Output = ();
65
66    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
67        let this = self.project();
68        this.scope.run_in(move || this.task.poll(cx).map(|_| ()))
69    }
70}
71
72impl<T: Future> ScopedFuture<T> {
73    #[cfg_attr(debug_assertions, track_caller)]
74    pub fn new_in_current_scope(f: T) -> Self {
75        let (abortable, handle) = abortable(f);
76        on_cleanup(move || handle.abort());
77
78        let scope = use_current_scope();
79
80        Self {
81            task: abortable,
82            scope,
83        }
84    }
85}