# Deference
[](https://hex.pm/packages/deference)
[](https://hexdocs.pm/deference)
A function deferring library inspired by [zig](https://zig.guide/language-basics/defer/)!
The main purpose is chaining operations which could fail for any number of reasons, while
also requiring cleanup operations to be performed. The most important bits are `with_defer/1`, `defer/1`, `err_defer/1`, and `throw_err/1`.
```ex
import Deference
def example() do
with_defer do
{:ok, user_id} =
API.User.create("really_cool_username", "really_cool_password")
err_defer do
API.User.delete("really_cool_username")
end
post_id =
API.Post.create("really_cool_username", "hello")
|> case do
{:ok, post_id} -> post_id
{:error, _reason} ->
# can't post, no reason to keep the user around
throw_err({:error, :failed_to_post})
end
err_defer do
API.Post.delete(post_id)
end
API.Post.edit(post_id, "hello\nedit: wow i didn't expect this to blow up")
|> case do
{:ok, post_id} -> :ok
{:error, _reason} ->
# failed to edit post, bail!
throw_err({:error, :failed_to_edit_post})
end
end
end
```
Deferred operations are collected and called in the reverse order they are specified in, and they come in two flavors: Error defers and standard defers.
Error defers are only called if `throw_err/1` is called or an exception occurs within the `with_defer` block. Standard defers are *always* called, even when the `with_defer` block exits normally.
In the case above, if the post failed to edit, then first it would call `API.Post.delete(post_id)`, then `API.User.delete("really_cool_username")`.
If `throw_err/1` is not called, then the block returns as normal, e.g:
```ex
with_defer do
defer do
:logger.error("this shouldn't happen!")
end
if 1 == 2 do
throw_err()
else
:ok
end
end
```
This will always resolve as `:ok` with no side effects.
`throw_err/1` also allows you an early return path. Whatever term is provided will be the return for the whole block, defaulting to `:error`.
```ex
with_defer do
# some stuff
throw_err({:error, :hello})
# some other stuff
:ok
end
```
Will always evaluate as `{:error, :hello}` stopping execution at the throw
A regular `throw/1` will return early as well, but without executing `err_defer`'d statements.
Some options are provided for convenience:
- a `rescue` clause can be provided directly in `with_defer`:
```ex
with_defer do
raise "Abort"
rescue
_ -> :saved
end
```
- `fwd` will call deferred functions in the order they were specified:
```ex
with_defer fwd: true do
defer do
:logger.debug("1")
end
defer do
:logger.debug("2")
end
defer do
:logger.debug("3")
end
end
```
will result in 1, 2, 3 being logged.
- `safe` will wrap the block in a try/call block, killing any exceptions:
```ex
with_defer safe: true do
raise "Abort"
end
```
will return `{:error, :exception}`
I don't recommend using this, but it's there! Instead, consider providing a rescue clause.