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
use cfg_if::cfg_if;
use std::future::Future;

/// Spawns and runs a thread-local [`Future`] in a platform-independent way.
///
/// This can be used to interface with any `async` code by spawning a task
/// to run a `Future`.
///
/// ## Limitations
///
/// You should not use `spawn_local` to synchronize `async` code with a
/// signal’s value during server rendering. The server response will not
/// be notified to wait for the spawned task to complete, creating a race
/// condition between the response and your task. Instead, use
/// [`create_resource`](crate::create_resource) and `<Suspense/>` to coordinate
/// asynchronous work with the rendering process.
///
/// ```
/// # use leptos::*;
/// # #[cfg(not(any(feature = "csr", feature = "serde-lite", feature = "miniserde", feature = "rkyv")))]
/// # {
///
/// async fn get_user(user: String) -> Result<String, ServerFnError> {
///     Ok(format!("this user is {user}"))
/// }
///
/// // ❌ Write into a signal from `spawn_local` on the serevr
/// #[component]
/// fn UserBad() -> impl IntoView {
///     let signal = create_rw_signal(String::new());
///
///     // ❌ If the rest of the response is already complete,
///     //    `signal` will no longer exist when `get_user` resolves
///     #[cfg(feature = "ssr")]
///     spawn_local(async move {
///         let user_res = get_user("user".into()).await.unwrap_or_default();
///         signal.set(user_res);
///     });
///
///     view! {
///         <p>
///             "This will be empty (hopefully the client will render it) -> "
///             {move || signal.get()}
///         </p>
///     }
/// }
///
/// // ✅ Use a resource and suspense
/// #[component]
/// fn UserGood() -> impl IntoView {
///     // new resource with no dependencies (it will only called once)
///     let user = create_resource(|| (), |_| async { get_user("john".into()).await });
///     view! {
///         // handles the loading
///         <Suspense fallback=move || view! {<p>"Loading User"</p> }>
///             // handles the error from the resource
///             <ErrorBoundary fallback=|_| {view! {<p>"Something went wrong"</p>}}>
///                 {move || {
///                     user.read().map(move |x| {
///                         // the resource has a result
///                         x.map(move |y| {
///                             // successful call from the server fn
///                             view! {<p>"User result filled in server and client: "{y}</p>}
///                         })
///                     })
///                 }}
///             </ErrorBoundary>
///         </Suspense>
///     }
/// }
/// # }
/// ```
pub fn spawn_local<F>(fut: F)
where
    F: Future<Output = ()> + 'static,
{
    cfg_if! {
        if #[cfg(all(target_arch = "wasm32", target_os = "wasi", feature = "ssr", feature = "spin"))] {
            spin_sdk::http::run(fut)
        }
        else if #[cfg(target_arch = "wasm32")] {
            wasm_bindgen_futures::spawn_local(fut)
        }
        else if #[cfg(any(test, doctest))] {
            tokio_test::block_on(fut);
        } else if #[cfg(feature = "ssr")] {
            use crate::Runtime;

            let runtime = Runtime::current();
            tokio::task::spawn_local(async move {
                crate::TASK_RUNTIME.scope(Some(runtime), fut).await
            });
        }  else {
            futures::executor::block_on(fut)
        }
    }
}