Managing Asynchronous Tasks in Yew for Real-time Updates with Cancellation

101 Views Asked by At

I'm currently working on a web application using the Yew framework, and I'm facing challenges in managing asynchronous tasks for real-time updates. The application involves displaying a dynamic list of items from a source. I want the application to work real-time. So I have set an interval between which I request for the latest value from the given source.

Also, the source of the items may be changed at any time when the callback updatedirectory is invoked.

#[function_component]
pub fn Pane() -> Html {
    let ApplicationContext {theme,sizes,settings} = use_context().unwrap();
    let directory = use_state(|| crate::app::data::directory());
    let entries = use_state(|| Entries::new());

    {
        let entries = entries.clone();
        let directory = directory.clone();

        use_effect(move || {
            yew::platform::spawn_local(async move {
                sleep(settings.refresh_wait()).await;
                /// some backend calls and setting of latest entries
            });
        })
    }

    let updatedirectory = Callback::from({
        let directory = directory.clone();
        move |_| {
            // sets a new state for the directory
        }
    });

    // entries is rendered here
}

Here are the problems with this code observed when updatedirectory is invoked

  1. The asynchronous tasks inside the spawn_local seem to pile up. When state changes, previous async tasks are not dropped and persist alongside the newest use_effect
  2. Unexpected behavior where items from different sources (directories) are displayed interchangeably, this may have been a side-effect of the former.

If only I could cancel the spawned task, I may not have any problems.

#[function_component]
pub fn Pane() -> Html {
    let ApplicationContext {theme,sizes,settings} = use_context().unwrap();
    let directory = use_state(|| crate::app::data::directory());
    let entries = use_state(|| Entries::new());
    let aborthandle = use_mut_ref(|| None);

    {
        let entries = entries.clone();
        let directory = directory.clone();
        let aborthandle = aborthandle.clone();

        use_effect(move || {
            let newhandle = tokio::task::spawn_local(async move {
                sleep(settings.refresh_wait()).await;
                /// some backend calls and setting of entries
            });
            if let Some(oldhandle) = aborthandle.borrow_mut().replace(newhandle) {
                oldhandle.abort();
            }
        })
    }

    let updatedirectory = Callback::from({
        let dir= directory.clone(); let entries = entries.clone();
        move |nd| {
            dir.set(nd);
            entries.set(Entries::new);
        }
    });

    // entries rendered here
}

But this runs into the runtime error:

panicked at '`spawn_local` called from outside of a `task::LocalSet`', src\app\cview\pane\mod.rs:34:30

Why do I get this error when yew::platform::spawn_local() also involves a call to tokio::task::spawn_local anyways?

How can I work around this?

If it is not plausible to use use_effect, what is a better way to achieve the desired functionality?

0

There are 0 best solutions below