View Source Migrating from Retry
Note
The following examples demonstrate how to preserve exact behavior when migrating from Retry to OnceMore. However, you may want to consider restructuring your retry logic instead of maintaining direct equivalence.
retry/2
# Retry
use Retry
retry with: delays do
call_service()
end
# OnceMore
import OnceMore.DelayStreams
OnceMore.retry(
&call_service/0,
&(&1 == :error or match?({:error, _reason}, &1)),
delays
)
RuntimeError
This and the following examples ignore the fact that, by default, Retry rescues RuntimeError
exception and treats it as a reason to retry. See Problems with Retry for why this behavior is undesirable.
Atoms and error tuples
If the block returns any of the atoms specified in
atoms
, a retry will be attempted. Other atoms or atom-result tuples will not be retried. Ifatoms
is not specified, it defaults to[:error]
.
from Retry docs
If you relied on atoms
option you can adjust should_retry_fn
as necessary:
# Retry
retry with: delays, atoms: [:error, :custom_error] do
call_service()
end
# OnceMore
@errors [:error, :custom_error]
OnceMore.retry(
&call_service/0,
&(&1 in @errors or match?({error, _reason} when error in @errors, &1)),
delays
)
Exceptions
Similary, if the block raises any of the exceptions specified in
rescue_only
, a retry will be attempted. Other exceptions will not be retried. Ifrescue_only
is not specified, it defaults to[RuntimeError]
.
from Retry docs
If you relied on Retry catching exceptions, with OnceMore you should catch your exceptions and translate them to values yourself:
# Imagine call_service/0 can raise `CustomException`
# Retry
retry with: delays, rescue_only: [CustomException] do
call_service()
end
# OnceMore
fn ->
try do
call_service()
rescue
e in CustomException -> {:error, e}
end
end
|> OnceMore.retry(&match?({:error, _reason}, &1), delays)
# Retry raises unresolved exceptions after delays are exhausted
|> then(fn
{:error, e} when is_exception(e, CustomException) -> raise e
result -> result
end)
after and else
The
after
block evaluates only when thedo
block returns a valid value before timeout. On the other hand, theelse
block evaluates only when thedo
block remains erroneous after timeout.
from Retry docs
If you relied on Retry remapping your results with after
and else
blocks, with OnceMore you should remap them yourself:
retry with: delays do
call_service()
after
{:ok, _value} -> :ok
else
{:error, _reason} -> :error
end
# OnceMore
&call_service/0
|> OnceMore.retry(&match?({:error, _reason}, &1), delays)
|> then(fn
{:ok, _value} -> :ok
{:error, _reason} -> :error
end)
retry_while/2
OnceMore returns both last result and accumulator while Retry returns only the accumulator. You need to adjust return values if you want to preserve that behavior.
# Retry
retry_while acc: 0, with: delays do
acc ->
case call_service() do
%{"errors" => true} -> {:cont, acc + 1}
result -> {:halt, result}
end
end
# OnceMore
fn acc ->
case call_service() do
%{"errors" => true} -> {:error, acc + 1}
result -> {:ok, result}
end
end
|> OnceMore.retry_with_acc(
fn result, _acc -> result == :error end,
0,
delays
)
|> then(fn
{:ok, result} -> result
{:error, acc} -> acc
end)
wait/2
Retry decides if retry is needed based on result being "falsey" (x in [false, nil]
). You can achieve the same behavior by passing Kernel.!/1
as a predicate to OnceMore.
Keep in mind that Retry wraps result in :ok/:error
tuple depending on if it's "truthy" or "falsey". You need to wrap it yourself if you want to preserve that behavior.
# Retry
wait delays do
get_available_service()
end
# OnceMore
&get_available_service/0
|> OnceMore.retry(&Kernel.!/1, delays)
|> then(fn result ->
if result do
{:ok, result}
else
{:error, result}
end
end)