I have some situation to use
try
do_something
{:ok, "good"}
catch
:exit -> {:error, "something break"}
rescue
e in ArgumentError -> {:failure, "some param is missing"}
end
It work , but just curious that’s a good way to go ?
I have some situation to use
try
do_something
{:ok, "good"}
catch
:exit -> {:error, "something break"}
rescue
e in ArgumentError -> {:failure, "some param is missing"}
end
It work , but just curious that’s a good way to go ?
It seems fine to me, though overall it is better not to have try
statements at all if it can be avoided (though many times it cannot).
I agree with @OvermindDL1, but I was wondering who is writing the do_something
function?
So with your example, are you the one writing do_something
or is it coming from an external library where you have no control over its internals? If you’re writing it, then you could restructure it like so with a case
statement for handling the control flow:
case do_something() do
:ok -> {:ok, "good"}
{:error, :exit} -> {:error, "something break"}
end
I really enjoyed the elixir-lang getting started documentation on try…rescue, where this very topic is discussed. I would recommend reading that whole page (I loved the whole guide), but the two parts that summarize it for me are:
In practice, however, Elixir developers rarely use the try/rescue construct.
and
In Elixir, we avoid using try/rescue because we don’t use errors for control flow. We take errors literally: they are reserved for unexpected and/or exceptional situations.
I’m sure there are other views out there, but coming from OOP world where I had try
blocks all over the place, I love () the approach used in Erlang/Elixir with tagged results, “happy paths”, and then optional constructs that use the
!
convention to indicate a non-tagged result (in a public API function).
(A “happy path” is a path of execution that returns the “:ok” tagged result.)
You can also start a Task to execute the do_something
and let that fail instead. Then you observe the behaviour externally:
task = Task.async(fn -> do_something() end)
case Task.yield(task, 5000) || Task.shutdown(task) do
{:ok, val} -> # ok
{:error, val} -> # error
nil -> # no result
end
actually, I just want to focus if in case of I want to split between an error such as no process found
and failure such as my command cannot do a proper action with user input
from the result of do_something
. I like to do it in elixir way, too.
This is interesting way, I should try this. Thank you But can I catch an error raised, such as ArgumentError in this way?
Wouldn’t this kill the process that started the task if do_something()
raised an exception?
iex> task = Task.async(fn -> raise ArgumentError, "oh no!" end)
[error] Task #PID<0.31331.0> started from #PID<0.31317.0> terminating
** (ArgumentError) oh no!
(elixir) lib/task/supervised.ex:94: Task.Supervised.do_apply/2
(elixir) lib/task/supervised.ex:45: Task.Supervised.reply/5
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Function: #Function<20.52032458/0 in :erl_eval.expr/5>
Args: []
** (EXIT from #PID<0.31317.0>) an exception was raised:
** (ArgumentError) oh no!
(elixir) lib/task/supervised.ex:94: Task.Supervised.do_apply/2
(elixir) lib/task/supervised.ex:45: Task.Supervised.reply/5
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Interactive Elixir (1.3.1) - press Ctrl+C to exit (type h() ENTER for help)
iex> task
** (CompileError) iex:1: undefined function task/0
Yep. Is Task.async supposed to to handle exceptions?
You are right. It should have been Task.Supervisor.async_nolink/1
. Although it requires a Task supervisor you will start your tasks on.
To any other noobs who come across this, here’s how to start a basic Task supervisor:
{:ok, task_supervisor} = Task.Supervisor.start_link()
Then you can play around with the other concepts explained in this thread:
task = Task.Supervisor.async_nolink(task_supervisor, fn -> :hello_world end)
result = Task.yield(task, 5000) || Task.shutdown(task) # {:ok, :hello_world}