Do you want to pick up from where you left of?
Take me there

Manejo de errores

Aunque es más común devolver una tupla {:error, reason}, Elixir soporta excepciones, y en esta lección revisaremos como manejar errores y los diferentes mecanismos a nuestra disposición.

En general la convención en Elixir es crear una función (example/1) que devuelve {:ok, result} y {:error, reason} y una función distinta (example!/1) que devuelve el resultado result sin envolver o levanta un error.

Esta lección se enfocará en interactuar con la última forma

Manejo de errores

Antes de que podamos manejar errores necesitamos crearlos, y la manera más simple de hacer esto es con raise/1:

iex> raise "Oh no!"
** (RuntimeError) Oh no!

Si queremos especificar el tipo y mensaje, necesitamos usar raise/2:

iex> raise ArgumentError, message: "the argument value is invalid"
** (ArgumentError) the argument value is invalid

Cuando sabemos que un error puede ocurrir, podemos manejarlo usando try/rescue y coincidencia de patrones:

iex> try do
...>   raise "Oh no!"
...> rescue
...>   e in RuntimeError -> IO.puts("An error occurred: " <> e.message)
...> end
An error occurred: Oh no!
:ok

Es posible coincidir múltiples errores en un solo rescue:

try do
  opts
  |> Keyword.fetch!(:source_file)
  |> File.read!()
rescue
  e in KeyError -> IO.puts("missing :source_file option")
  e in File.Error -> IO.puts("unable to read source file")
end

After

A veces es necesario realizar alguna acción después de nuestro try/rescue independientemente de si hubo o no error. Para esto tenemos try/after. Si estás familiarizado con Ruby, esto es similar a begin/rescue/ensure o en Java a try/catch/finally:

iex> try do
...>   raise "Oh no!"
...> rescue
...>   e in RuntimeError -> IO.puts("An error occurred: " <> e.message)
...> after
...>   IO.puts "The end!"
...> end
An error occurred: Oh no!
The end!
:ok

Esto es más comúnmente usado con archivos o conexiones que deberían ser cerradas:

{:ok, file} = File.open("example.json")

try do
  # Do hazardous work
after
  File.close(file)
end

Nuevos errores

Pese a que elixir incluye un número de errores predefinidos como RuntimeError, mantenemos la habilidad de crear los nuestros si necesitamos algo específico. Crear un nuevo error es fácil con la macro defexception/1, que convenientemente acepta la opción :message para definir un mensaje de error por defecto:

defmodule ExampleError do
  defexception message: "an example error has occurred"
end

Llevemos nuestro nuevo error a dar una vuelta:

iex> try do
...>   raise ExampleError
...> rescue
...>   e in ExampleError -> e
...> end
%ExampleError{message: "an example error has occurred"}

Throws

Otro mecanismo para trabajar con errores en Elixir es throw y catch. En la práctica, estos ocurren muy inusualmente en código de Elixir más nuevo, pero es importante conocerlos y entenderlos.

La función throw/1 nos da la habilidad de salir de la ejecución con un valor específico que podemos capturar con catch y luego utilizar:

iex> try do
...>   for x <- 0..10 do
...>     if x == 5, do: throw(x)
...>     IO.puts(x)
...>   end
...> catch
...>   x -> "Caught: #{x}"
...> end
0
1
2
3
4
"Caught: 5"

Como se mencionó, throw/catch son muy poco comunes y típicamente existen como recurso provisional cuando las bibliotecas fallan en proveer APIs adecuadas.

Salir

El último mecanismo de error que Elixir nos provee es exit. Las señales de salida ocurren cuando un proceso muere y son una parte importante de la tolerancia a fallos en Elixir.

Para salir de manera explícita podemos usar exit/1:

iex> spawn_link fn -> exit("oh no") end
** (EXIT from #PID<0.101.0>) evaluator process exited with reason: "oh no"

Pese a que es posible manejar una salida con try/catch, hacerlo es extremadamente raro. En casi todos los casos es ventajoso permitir al supervisor manejar la salida del proceso:

iex> try do
...>   exit "oh no!"
...> catch
...>   :exit, _ -> "exit blocked"
...> end
"exit blocked"
¿Encontraste un error o quieres contribuir a la lección? ¡Edita esta lección en GitHub!