I have tried to use combination of catch and rescue, curious that is a good way

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 ?

3 Likes

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). :slight_smile:

3 Likes

I agree with @OvermindDL1, but I was wondering who is writing the do_something function? :thinking:

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 (:heart:) 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.)

3 Likes

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
6 Likes

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.

2 Likes

This is interesting way, I should try this. Thank you :slight_smile: But can I catch an error raised, such as ArgumentError in this way?

1 Like

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?

3 Likes

You are right. It should have been Task.Supervisor.async_nolink/1. Although it requires a Task supervisor you will start your tasks on.

3 Likes