async_with alternatives and similar packages
Based on the "Utilities" category.
Alternatively, view async_with alternatives based on common mentions on social networks and blogs.
-
retry
Simple Elixir macros for linear retry, exponential backoff and wait with composable delays -
erlware_commons
Erlware Commons is an Erlware project focused on all aspects of reusable Erlang components. -
plasm
Ecto's composable query multitool (.count, .random, .earliest, .latest, .find, .at, .on, etc.) -
sips_downloader
Utility to download Elixir Sips screencast videos written in Elixir (subscription to Elixir Sips required) -
ar2ecto
Migrate your active record migrations to ecto compatible migrations -
dot-notes
Simple dot/bracket notation parsing/conversion for Maps/Lists -
fitex
FitEx is a Macro-Module which provides a bit of sugar for function definitions. -
exjprop
Elixir library for reading Java properties files from various sources. -
ExNric
Validation for National Registration Identity Card numbers (NRIC) -
ex_progress
A library for tracking progress across many tasks and sub-tasks
Clean code begins in your IDE with SonarLint
Do you think we are missing an alternative of async_with or a related project?
README
AsyncWith
The asynchronous version of Elixir's with
, resolving the dependency graph and executing the clauses in the most performant way possible!
Installation
Add async_with
to your project's dependencies in mix.exs
:
def deps do
[{:async_with, "~> 0.3"}]
end
And fetch your project's dependencies:
$ mix deps.get
Usage
TL;DR: use AsyncWith
and just write async
in front of with
.
async with
always executes the right side of each clause inside a new task. Tasks are spawned as soon as all the tasks that it depends on are resolved. In other words, async with
resolves the dependency graph and executes all the clauses in the most performant way possible. It also ensures that, if a clause does not match, any running task is shut down.
Let's start with an example:
iex> use AsyncWith
iex>
iex> opts = %{width: 10, height: 15}
iex> async with {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height) do
...> {:ok, width * height}
...> end
{:ok, 150}
As in with/1
, if all clauses match, the do
block is executed, returning its result. Otherwise the chain is aborted and the non-matched value is returned:
iex> use AsyncWith
iex>
iex> opts = %{width: 10}
iex> async with {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height) do
...> {:ok, width * height}
...> end
:error
In addition, guards can be used in patterns as well:
iex> use AsyncWith
iex>
iex> users = %{"melany" => "guest", "bob" => :admin}
iex> async with {:ok, role} when not is_binary(role) <- Map.fetch(users, "bob") do
...> :ok
...> end
:ok
Variables bound inside async with
won't leak; "bare expressions" may also be inserted between the clauses:
iex> use AsyncWith
iex>
iex> width = nil
iex> opts = %{width: 10, height: 15}
iex> async with {:ok, width} <- Map.fetch(opts, :width),
...> double_width = width * 2,
...> {:ok, height} <- Map.fetch(opts, :height) do
...> {:ok, double_width * height}
...> end
{:ok, 300}
iex> width
nil
An else
option can be given to modify what is being returned from async with
in the case of a failed match:
iex> use AsyncWith
iex>
iex> opts = %{width: 10}
iex> async with {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height) do
...> {:ok, width * height}
...> else
...> :error ->
...> {:error, :wrong_data}
...> end
{:error, :wrong_data}
If an else
block is used and there are no matching clauses, an AsyncWith.ClauseError
exception is raised.
Order-dependent clauses that do not express their dependency via their used or defined variables could lead to race conditions, as they are executed in separated tasks:
use AsyncWith
async with Agent.update(agent, fn _ -> 1 end),
Agent.update(agent, fn _ -> 2 end) do
Agent.get(agent, fn state -> state end) # 1 or 2
end
Check the documentation for more information.
Documentation
Documentation is available at https://hexdocs.pm/async_with
Code formatter
As described in Code.format_string!/2
documentation, Elixir will add parens to all calls except for:
- calls that have do/end blocks
- local calls without parens where the name and arity of the local call is also listed under
:locals_without_parens
async with
expressions should fall under the first category and be kept without parens, because they are similar to with/1
calls.
This is then the recommended .formatter.exs
configuration:
[
# Regular formatter configuration
# ...
import_deps: [:async_with]
]
As an alternative, you can add async: 1
and async: 2
directly to the list :locals_without_parens
.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/fertapric/async_with. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
Running tests
Clone the repo and fetch its dependencies:
$ git clone https://github.com/fertapric/async_with.git
$ cd async_with
$ mix deps.get
$ mix test
Building docs
$ mix docs
Acknowledgements
I would like to express my gratitude to all the people in the Elixir Core Mailing list who gave ideas and feedback on the early stages of this project. A very special mention to Luke Imhoff (@KronicDeth), Theron Boerner (@hunterboerner), and John Wahba (@johnwahba).
Copyright and License
(c) Copyright 2017-2019 Fernando Tapia Rico
AsyncWith source code is licensed under the [MIT License](LICENSE).
*Note that all licence references and agreements mentioned in the async_with README section above
are relevant to that project's source code only.