defmodule Rephex.AsyncAction.Base do
@moduledoc """
Define behavior of AsyncAction.
"""
alias Phoenix.LiveView.Socket
# Path to target AsyncResult
@type result_path :: [term()]
@type state :: map()
@type payload :: map()
@type progress :: any()
@type success_result :: any()
@type exit_reason :: any()
@type failed_value :: any()
@doc """
Get initial progress. This value will be set synchronously before the async action starts.
## Example
@impl true
def initial_progress(_path, _payload) do
# optional but recommended
# `start/4` apply this progress synchronously.
# AsyncResult.loading will be `{progress, _meta_values}` before start_async.
{0, 100}
end
"""
@callback initial_progress(result_path(), payload()) :: progress()
@doc """
Before the async action starts, this callback will be called.
## Example
@impl true
def before_start(socket, _result_path, %{amount: _amount} = _payload) do
# optional
# This function will be called before `start_async`.
socket |> LiveView.put_flash(:info, "Add twice start")
end
"""
@callback before_start(Socket.t(), result_path(), payload()) :: Socket.t()
@doc ~S"""
After the async action is resolved, this callback will be called.
## Example
@impl true
def after_resolve(socket, _result_path, result) do
# optional
# This function will be called after `start_async` is finished.
case result do
{:ok, amount} ->
socket
|> State.add_count(%{amount: amount})
|> LiveView.put_flash(:info, "Add twice done: #{amount}")
{:exit, _reason} ->
socket
|> LiveView.put_flash(:error, "Add twice failed")
end
end
"""
@callback after_resolve(
Socket.t(),
result_path(),
{:ok, success_result()} | {:exit, exit_reason()}
) :: Socket.t()
@doc ~S"""
Generate failed value. This value will be set to AsyncResult via `AsyncResult.failed/2`.
## Example
@impl true
def generate_failed_value(_result_path, exit_reason) do
# optional
# You can customize the failed value.
case exit_reason do
{:shutdown, :cancel} -> "canceled by no-reason"
{:shutdown, {:cancel, text}} when is_bitstring(text) -> "canceled by #{text}"
_ -> "unknown reason"
end
end
"""
@callback generate_failed_value(result_path(), exit_reason()) :: failed_value()
@doc """
Start async action.
## Example
@impl true
def start_async(_state, _path, %{amount: amount} = _payload, progress) do
# required
# This function will be passed to Phoenix's `start_async`.
max = 500
progress.({0, max})
1..max
|> Enum.each(fn i ->
:timer.sleep(2)
progress.({i, max})
end)
amount
end
"""
@callback start_async(
state(),
result_path(),
payload(),
(progress() -> nil)
) :: success_result()
@doc """
This callback will be implemented by __using__.
"""
@callback options() :: %{optional(:throttle) => non_neg_integer()}
@optional_callbacks initial_progress: 2,
before_start: 3,
after_resolve: 3,
generate_failed_value: 2
end