fertapric/async_with
{ "createdAt": "2017-08-17T11:37:20Z", "defaultBranch": "master", "description": "The asynchronous version of Elixir's \"with\", resolving the dependency graph and executing the clauses in the most performant way possible!", "fullName": "fertapric/async_with", "homepage": "https://hexdocs.pm/async_with", "language": "Elixir", "name": "async_with", "pushedAt": "2023-04-10T11:58:38Z", "stargazersCount": 157, "topics": [ "async", "elixir" ], "updatedAt": "2025-05-04T01:15:35Z", "url": "https://github.com/fertapric/async_with"}AsyncWith
Section titled “AsyncWith”The asynchronous version of Elixir’s with, resolving the dependency graph and executing the clauses in the most performant way possible!
Installation
Section titled “Installation”Add async_with to your project’s dependencies in mix.exs:
def deps do [{:async_with, "~> 0.3"}]endAnd fetch your project’s dependencies:
$ mix deps.getTL;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 AsyncWithiex>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 AsyncWithiex>iex> opts = %{width: 10}iex> async with {:ok, width} <- Map.fetch(opts, :width),...> {:ok, height} <- Map.fetch(opts, :height) do...> {:ok, width * height}...> end:errorIn addition, guards can be used in patterns as well:
iex> use AsyncWithiex>iex> users = %{"melany" => "guest", "bob" => :admin}iex> async with {:ok, role} when not is_binary(role) <- Map.fetch(users, "bob") do...> :ok...> end:okVariables bound inside async with won’t leak; “bare expressions” may also be inserted between the clauses:
iex> use AsyncWithiex>iex> width = niliex> 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> widthnilAn else option can be given to modify what is being returned from async with in the case of a failed match:
iex> use AsyncWithiex>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 2endCheck the documentation for more information.
Documentation
Section titled “Documentation”Documentation is available at https://hexdocs.pm/async_with
Code formatter
Section titled “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
Section titled “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
Section titled “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 testBuilding docs
Section titled “Building docs”$ mix docsAcknowledgements
Section titled “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
Section titled “Copyright and License”(c) Copyright 2017-2019 Fernando Tapia Rico
AsyncWith source code is licensed under the [MIT License]!(LICENSE).