Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/phoenix_live_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2245,8 +2245,8 @@ defmodule Phoenix.LiveView do
an `Phoenix.LiveView.AsyncResult` struct holding the status of the operation
and the result when the function completes.

The function must return either a map or a keyword list with the assigns
to merge into the socket.
The function must return either `{:ok, assigns}` or `{:error, reason}`,
where `assigns` is a map with the keys passed to `assign_async/3`.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A keyword list should work

Suggested change
where `assigns` is a map with the keys passed to `assign_async/3`.
where `assigns` is a map or keyword list with the keys passed to `assign_async/3`.

Copy link
Copy Markdown
Contributor Author

@preciz preciz May 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't work when keyword list is used:

{:ok, %{} = assigns} ->

Also just tested it manually again.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I guess the list check here is unnecessary then:

{:ok, {:ok, assigns}} when is_map(assigns) or is_list(assigns) ->
- if it never worked, we can adjust the documentation indeed


The task is only started when the socket is connected.

Expand Down
2 changes: 1 addition & 1 deletion lib/phoenix_live_view/async.ex
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ defmodule Phoenix.LiveView.Async do

defp handle_kind(socket, _maybe_component, :assign, keys, result) do
case result do
{:ok, {:ok, assigns}} when is_map(assigns) or is_list(assigns) ->
{:ok, {:ok, assigns}} when is_map(assigns) ->
new_assigns =
for {key, val} <- assigns do
{key, AsyncResult.ok(get_current_async!(socket, key), val)}
Expand Down
13 changes: 12 additions & 1 deletion test/phoenix_live_view/integrations/assign_async_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ defmodule Phoenix.LiveView.AssignAsyncTest do
assert render(lv)
end

test "keyword list return", %{conn: conn} do
{:ok, lv, _html} = live(conn, "/assign_async?test=bad_keyword")

assert render_async(lv) =~
"expected assign_async to return {:ok, map} of\\nassigns for [:data] or {:error, reason}, got: {:ok, [data: 123]}"

assert render(lv)
end

test "valid return", %{conn: conn} do
{:ok, lv, _html} = live(conn, "/assign_async?test=ok")
assert render_async(lv) =~ "data: 123"
Expand Down Expand Up @@ -222,7 +231,9 @@ defmodule Phoenix.LiveView.AssignAsyncTest do

test "valid return", %{conn: conn} do
{:ok, lv, _html} = live(conn, "/assign_async?test=sup_ok")
assert render_async(lv) =~ "data: 123"
html = render_async(lv)
assert html =~ "data: 123"
refute html =~ "expected assign_async to return"
Comment on lines +235 to +236
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this assertion actually relied on the error message. A good example for a bad test 😅

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it seems like.

end

test "raise during execution", %{conn: conn} do
Expand Down
6 changes: 5 additions & 1 deletion test/support/live_views/assign_async.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,17 @@ defmodule Phoenix.LiveViewTest.Support.AssignAsyncLive do
{:ok, assign_async(socket, :data, fn -> {:ok, %{bad: 123}} end)}
end

def mount(%{"test" => "bad_keyword"}, _session, socket) do
{:ok, assign_async(socket, :data, fn -> {:ok, data: 123} end)}
end

def mount(%{"test" => "ok"}, _session, socket) do
{:ok, assign_async(socket, :data, fn -> {:ok, %{data: 123}} end)}
end

def mount(%{"test" => "sup_ok"}, _session, socket) do
{:ok,
assign_async(socket, :data, fn -> {:ok, data: 123} end, supervisor: TestAsyncSupervisor)}
assign_async(socket, :data, fn -> {:ok, %{data: 123}} end, supervisor: TestAsyncSupervisor)}
end

def mount(%{"test" => "raise"}, _session, socket) do
Expand Down
Loading