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

Traitement des erreurs

Bien qu’il soit commun pour les fonctions de retourner un tuple {:error, reason}, Elixir supporte les exceptions. Dans cette leçon, nous expliquons comment prendre en charge les erreurs, et nous passerons en revue les mécanismes disponibles.

En général, la convention en Elixir est de créer une fonction (example/1) qui retourne {:ok, result} ou {:error, reason}, et une fonction à part (example!/1) qui retourne simplement result ou lève une erreur. Nous nous concentrerons sur le second cas dans cette leçon.

Conventions générales

Pour l’instant, la communauté d’Elixir a quelques conventions quant aux valeurs retournées par les fonctions :

Généralement, nous traitons le premier cas par Pattern Matching ; mais, dans cette leçon, nous nous concentrons sur le second cas : les exceptions.

Souvent, dans les API publiques, nous trouvons également une seconde version d’une fonction avec un ! en suffixe (p. ex. example!/1). Dans ce cas, la convention est que cette fonction lève une erreur ou retourne directement le résultat.

Traitement d’une erreur

Avant de traiter des erreurs, nous devons les créer, et le moyen le plus simple pour ce faire est d’utiliser raise/1 :

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

Si nous voulons spécifier le type et le message de l’erreur, nous devons utiliser raise/2 :

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

Quand nous savons qu’une erreur peut survenir, nous pouvons l’intercepter avec le couple try et rescue, et du Pattern Matching :

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

Un même rescue peut intercepter plusieurs types d’erreurs :

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

Suite du traitement d’une erreur

Il peut être nécessaire de réaliser des actions après un couple try et rescue, peu importe qu’une erreur ait été interceptée ou non. Dans ce cas, nous disposons de after. Si vous êtes familier avec Ruby, cela correspond au triptyque begin/rescue/ensure, ou à try/catch/finally en Java.

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

after est communément utilisé quand il faut fermer des fichiers ou des connexions :

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

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

Nouveaux types d’exceptions

Si Elixir comptent plusieurs types d’exceptions dans sa bibliothèque standard, telle que RuntimeError, nous avons la capacité de définir nos propres exceptions si besoin.

Créer une nouvelle exception passe par la macro defexception/1. Cette macro accepte l’option :message pour définir un message par défaut :

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

Utilisons notre nouvelle exception :

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

Lever une exception

Un autre mécanisme pour traiter des erreurs en Elixir est le couple throw et catch. Il est peu commun de croiser ces mots-clés dans du code Elixir récent, mais il est utile de les connaître et de les comprendre.

La fonction throw/1 permet d’interrompre l’exécution d’une fonction avec une valeur particulière, interceptable grâce à catch :

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"

Comme nous l’avons indiqué, throw et catch sont plutôt rares et existent pour pallier aux insuffisances de certaines bibliothèques.

Interrompre l’exécution du programme

Le dernier mécanisme d’erreur en Elixir est exit, qui permet de sortir du programme. Un signal de sortie est émis quand un processus meurt, et c’est une partie importante de la tolérance aux pannes en Elixir.

Pour explicitement sortir du programme, nous utilisons exit/1 :

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

Bien qu’il soit possible d’interception un signal de sortie avec try et catch, il est extrêmement rare d’y avoir recours. Dans quasiment tous les cas, il est judicieux de laisser au superviseur le traitement des signaux de sortie :

iex> try do
...>   exit "oh no!"
...> catch
...>   :exit, _ -> "exit blocked"
...> end
"exit blocked"
Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!