We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Asynchronous operations in Phoenix LiveView
Deankinyua
What do you understand by the term “Expensive Computations”? Theoretical computer science defines them as the ones taking the most memory and time to complete. If this is the case, then executing these tasks the ‘normal way’ will block other subsequent events from getting processed. There has to be a better way! Asynchronous (non-blocking) tasks = the way. These execute in the background. Because there is no guarantee that the task will complete successfully, asynchronous tasks are normally done inside independent processes so that if anything goes wrong only that process will be affected. You can use the Task
module to spawn an asynchronous task and send a message to the parent process once complete:
caller = self()
Task.start(fn ->
{:ok, {description, list_of_episodes}} = Rag.generate(query)
send(caller, {:generation_done, {description, list_of_episodes, query}})
end)
The Task
module is pretty convenient for most asynchronous tasks but there are better alternatives if you are inside LiveView. I’m talking about the assign_async/3 and start_async/3 functions.
You get process monitoring when using this 2 functions which means you can handle process crashes gracefully, loading states can be matched on the UI e.t.c. The big question now is when to choose one over the other. Assign_async/3 is the most simple to understand and it is better used when you just need to update the UI after some processing is done. Consider a situation where you need to load some data on mount and it takes a long time doing so. You can wrap the DB logic inside an assign_async:
assign_async(socket, :total_sales, fn -> {:ok, %{total_sales: fetch_sales!(product_id)}} end)
Then show loading state conditionally on the UI:
<div :if={@total_sales.loading}>Calculating the total sales...</div>
Look up this for a better understanding of assign_async/3.
Start_async is used when you need more lower-level control. I use it mostly when I need to do something with the result of the operation. Consider:
start_async(socket, :prompt_results, fn ->
Rag.generate(query)
end)
You can then handle success or exit scenarios using the handle_async/3 callbacks:
@impl Phoenix.LiveView
def handle_async(:prompt_results, {:ok, prompt_results}, socket) do
case prompt_results do
{:ok, {response, podcast_episodes, embedding}} ->
handle_prompt_results(response, podcast_episodes, embedding, socket)
{:error, _reason} ->
{:noreply,
put_flash(
socket,
:error,
"An error occurred while processing your prompt. Please try again."
)}
end
end
# in case the process crashes provide meaningful feedback to your user
def handle_async(:prompt_results, {:exit, _reason}, socket) do
send(self(), {:loading_state, false})
{:noreply,
put_flash(
socket,
:error,
"An error occurred while processing your prompt. Please try again."
)}
end
Copy link
copied to clipboard