DepMulti (dep_multi v0.1.0) View Source

DepMulti is a data structure for performing dependant asynchronous operations.

To demonstrate the problem, image you have three asynchronous tasks:

## [

Task.async(fn -> do_some_work() end),
Task.async(fn -> do_some_other_work() end),
Task.async(fn -> do_more_work() end),

] |> Enum.map(&Task.await/1)

This works until you have a dependency / ordering issue. Say do_more_work must happen after do_some_work. At that point, you'll need to name each task and reference those names in other tasks that are dependant on them. If those dependant task needs data from a previous task, you'll also want pass those changes into the function.

The API to solve this issue is based on Ecto.Multi(https://hexdocs.pm/ecto/Ecto.Multi.html), and specifically run/3 and run/5. The differences are:

  • The tasks are executed asychronously
  • the run methods take a list of dependencies and a timeout
  • the changes passed into the function only include direct or indirect dependencies

Example

iex> DepMulti.new() ...> |> DepMulti.run(:step1, [], fn -> ...> {:ok, 1} ...> end) ...> |> DepMulti.run(:step_2a, [dependencies: [:step_1], timeout: 5000], fn %{step_1: value} -> ...> {:ok, 2 + value} ...> end) ...> |> DepMulti.run(:step_2b, [dependencies: [:step_1]], Map, :fetch, [:step_1]) ...> |> DepMulti.execute() {:ok, %{step_1: 1, step_2a: 3, step_2b: 1}}

Link to this section Summary

Functions

Returns an empty DepMulti struct.

Adds a function to run as part of the multi.

Adds a function to run as part of the multi.

Returns the list of operations stored in multi.

Link to this section Types

Specs

changes() :: map()

Specs

dependencies() :: [name()]

Specs

name() :: any()

Specs

names() :: MapSet.t()

Specs

operations() :: [DepMulti.Operation.t()]

Specs

run() :: (changes() -> {:ok | :error, any()}) | {module(), atom(), [any()]}

Specs

run_cmd() :: {:run, run()}

Specs

t() :: %DepMulti{names: names(), operations: operations()}

Link to this section Functions

Link to this function

execute(multi, opts \\ [])

View Source

Specs

execute(t(), keyword()) ::
  {:ok, changes()}
  | {:error, name(), any(), changes()}
  | {:terminate, name(), any(), changes()}

Specs

new() :: t()

Returns an empty DepMulti struct.

Example

iex> DepMulti.new() |> DepMulti.to_list()
[]
Link to this function

run(multi, name, opts, run)

View Source

Specs

run(t(), name(), keyword(), run()) :: t()

Adds a function to run as part of the multi.

The function should return either {:ok, value} or {:error, value}, and receives the changes so far as the only argument.

NOTE: The changes will only include direct or indirect dependencies

Example

DepMulti.run(multi, :write, [:image], fn %{image: image} ->
  with :ok <- File.write(image.name, image.contents) do
    {:ok, nil}
  end
end)
Link to this function

run(multi, name, opts, mod, fun, args)

View Source

Specs

run(t(), name(), keyword(), module(), function, args) :: t()
when function: atom(), args: [any()]

Adds a function to run as part of the multi.

Similar to run/4, but allows to pass module name, function and arguments. The function should return either {:ok, value} or {:error, value}, and receives the rthe changes so far as the first argument (prepended to those passed in the call to the function).

NOTE: The changes will only include direct or indirect dependencies

Specs

to_list(t()) :: operations()

Returns the list of operations stored in multi.