Send a long-running task inside of a LiveView process without blocking it but knowing when that long-running task ends

Hello. I have a doubt. I’m in a LiveView process. How can I send a long-running task inside of that process without blocking it but I still want to know when that long-running task ends? That’s because I have a loading spinner that would be in true state until it finishes. Blocking the LV process until everything finishes is the easiest thing but I must not do it because I have a PubSub that updates the socket asynchronously.
loading is the name of my assign for the loading spinner state
Here’s my code:

# the handle_info was called like this
...
 send(self(), {:fetch_from_api, contents})

 {:noreply, assign(socket, loading: true)}
end

@impl Phoenix.LiveView
def handle_info({:fetch_from_api, contents}, socket) do
    # loops through a list and fetches result from API and inserts the on DB
    # as soon as they are fetched
    Task.start(fn -> MyAPI.fetch_results(contents) end)
    {:noreply, assign(socket, loading: false)}
  end
end

My problem is that my loading spinner doesn’t show because Task.start(fn -> MyAPI.fetch_results(contents) end) opens another process
Removing the Task part solves the problem because my loading spinner will end when MyAPI.fetch_results(content) ends
But I cannot do it synchronously because I can’t block this LV process. Why? Because I have a PubSub showing me results in a HTML table asynchronously every time the data is inserted in the DB. If I do that my spinner will be true until the processing is being done but the data will be inserted at the end of that processing.

I’d appreciate any help if someone knows

have the task send a message back to the LV process when it is finished. Set loading to false when that message is received.

1 Like

Just Process.monitor/1 it:

https://hexdocs.pm/elixir/Process.html#monitor/1

I would leverage a TaskSupervisor to run the task, and then implement the necessary handle_info callbacks to handle the success and failure cases: Task.Supervisor — Elixir v1.12.3

4 Likes

As I mentioned in the slack (and was asked to post here) the (quick and easy) solution is to send a message back to the live view process from the task process, like this:

def handle_info({:fetch_from_api, contents}, socket) do
  view = self()
  Task.start(fn ->
    MyAPI.fetch_results(contents)
    send(view, :fetch_from_api_complete)
  end)
  {:noreply, socket}
end

def handle_info(:fetch_from_api_complete, socket) do
  {:noreply, assign(socket, loading: false)}
end

I agree with @akoutmos that you may want to supervise the task in future to ensure you know its run succesfully.

3 Likes