Reputation: 4698
Both try/rescue
and try/catch
are error handling techniques in Elixir. According the corresponding chapter in the introduction guide.
Errors can be rescued using the
try/rescue
construct
On the other hand,
throw
andcatch
are reserved for situations where it is not possible to retrieve a value unless by usingthrow
andcatch
.
I have a brief understanding that rescue
is for errors. While catch
is for any value.
However,
throw
and catch
'?Upvotes: 49
Views: 18136
Reputation: 657
try/catch
has to be used when an error is known (like request validation errors) and raise/rescue
has to be used to catch exceptions (exceptions are unknown or unhandled errors).
A combination of both of these can be used. For example,
def example(conn, params) do
try do
data = %{}
types = %{field1: :string, field2: :integer}
changeset =
{data, types}
|> cast(params, [:field1, :field2])
|> validate_required([:field1, :field2])
if (!changeset.valid?) do
throw({:ClientError, changeset.errors })
end
# some logic
raise ArgumentError
catch
# client error is caught here
{:ClientError, error} ->
IO.inspect(error, label: "client error")
rescue
exception ->
# Argument error is caught here
IO.inspect(exception, label: "exception")
end
end
Upvotes: 3
Reputation: 41
I like to use the analogy:
You either catch
a thrown ball or rescue
someone from a mountain.
catch
- is expected and used for control flow (e.g. Java-like error handling)rescue
- for unexpected errors (e.g. runtime errors)Upvotes: 4
Reputation: 389
By reading Dimagog's answer, and the article found at https://inquisitivedeveloper.com/lwm-elixir-48/, I really gained a lot of insight on the matter. I am just sharing a personal practical example,
chset =
%SomeModel{}
|> SomeModel.changeset(attrs)
try do
chset
|> Repo.insert()
catch :error, %Postgrex.Error{postgres: %{code: :invalid_password}} ->
{ :error ,
chset
|> Changeset.add_error(:username, "may be invalid")
|> Changeset.add_error(:password, "may be invalid")
}
else
{:ok, lr} -> {:ok, Map.put(lr, :password, nil)}
error -> error
end
The postgresql error code comes from a plpgsql
function in which I raise an error, as follows,
raise invalid_password using
message = 'Invalid username or password' ,
detail = 'A user could not be found that matched the supplied username and password';
Upvotes: 1
Reputation: 1925
Other answers already cover usage of raise
vs. throw
well.
I'll describe the mechanics of how to handle each exceptional situation using a table:
creating | handling with | where y is
-----------------------------------------------------
raise x | rescue y | %RuntimeError{message: x}
error(x) | rescue y | %ErlangError{original: x}
throw x | catch y | x
exit(x) | catch :exit, y | x
where error(x)
is actually :erlang.error(x)
.
On top of this, both rescue
and catch/1
(catch with 1 argument) are just a syntactic sugar. All 4 cases above could be handled with catch/2
:
creating | handling with | where y is | and z is
-----------------------------------------------------------------
raise x | catch y, z | :error | %RuntimeError{message: x}
error(x) | catch y, z | :error | x
throw x | catch y, z | :throw | x
exit(x) | catch y, z | :exit | x
Note the asymmetry of handling raise
and error
with rescue
vs. catch/2
: x
is wrapped into %ErlangError
when rescue
is used, but not with catch/2
.
Upvotes: 41
Reputation: 2813
It's a good question.After research a bit.
What is the differences between them in details?
José's answer:
Mainly, you should use
throw
for control-flow and reserveraise
for errors, which happens on developer mistakes or under exceptional circumstances.In Elixir, this distinction is rather theoretical, but they matter in some languages like Ruby, where using errors/exceptions for control-flow is expensive because creating the exception object and backtrace is expensive.
Please check this answer Which situations require throw catch in Elixir
Shortly:
raise/rescue
Consider raise/rescue to be explicitly about exception handling (some unexpected situation like programmer errors, wrong environment, etc).
throw/catch
Is useful in places where you have expected failures. Classic examples are:
The last one:
Let's say you are trying to running some code from a process that is supervised by a Supervisor
but the process dies for an unexpected reason.
try do
IO.inspect MayRaiseGenServer.maybe_will_raise
rescue
RuntimeError -> IO.puts "there was an error"
end
MayRaiseGenServer
is supervised by a Supervisor
and for some reason an error was raised:
try do
IO.inspect MayRaiseGenServer.maybe_will_raise # <- Code after this line is no longer executed
And then you can come up with using catch an exception here:
try do
IO.inspect MayRaiseGenServer.maybe_will_raise
catch
:exit, _ -> IO.puts "there was an error"
end
Ok.Hope that clarify enough what we are looking for.
Upvotes: 35