web-dev-qa-db-fra.com

Elixir - essayer / attraper vs essayer / sauver?

Contexte

Tous les deux try/rescue et try/catch sont des techniques de gestion des erreurs dans Elixir. Selon le chapitre correspondant dans le guide d'introduction.

Les erreurs peuvent être récupérées en utilisant le try/rescue construire

D'autre part,

throw et catch sont réservés aux situations où il n'est pas possible de récupérer une valeur sauf en utilisant throw et catch.

Les doutes

J'ai une brève compréhension que rescue est pour les erreurs. Alors que catch est pour n'importe quelle valeur.

Cependant,

  • Quand dois-je utiliser les mécanismes de gestion des erreurs dans Elixir?
  • Quelles sont les différences entre eux en détail?
  • Comment en choisir un à utiliser dans un cas d'utilisation spécifique?
  • Quelles sont exactement 'les situations où il n'est pas possible de récupérer une valeur sauf en utilisant throw et catch' ?
27
Gavin

C'est une bonne question, après un peu de recherche.

  • Quelles sont les différences entre eux dans les détails?

    Réponse de José:

Principalement, vous devez utiliser throw pour le contrôle de flux et réserver raise pour les erreurs, ce qui se produit lors d'erreurs de développement ou dans des circonstances exceptionnelles.

Dans Elixir, cette distinction est plutôt théorique, mais elle est importante dans certains langages comme Ruby, où l'utilisation d'erreurs/exceptions pour le contrôle de flux est coûteuse car la création de l'objet d'exception et de la trace est coûteuse.

  • Comment en choisir un à utiliser dans un cas d'utilisation spécifique?

Veuillez vérifier cette réponse Quelles situations nécessitent une prise de jet dans Elixir

Prochainement:

raise/rescue

Considérez augmenter/sauvetage pour être explicitement sur la gestion des exceptions (certaines situations inattendues comme des erreurs de programmation, un mauvais environnement, etc.).

throw/catch

Est utile dans les endroits où vous avez prévu des échecs. Les exemples classiques sont:

Le dernier:

  • Quelles sont exactement "les situations où il n'est pas possible de récupérer une valeur sauf en utilisant throw et catch"?

Supposons que vous essayez d'exécuter du code à partir d'un processus supervisé par un Supervisor mais que le processus meurt pour une raison inattendue.

try do
IO.inspect MayRaiseGenServer.maybe_will_raise
rescue
  RuntimeError -> IO.puts "there was an error"
end

MayRaiseGenServer est supervisé par un Supervisor et pour une raison quelconque, une erreur s'est produite:

try do
IO.inspect MayRaiseGenServer.maybe_will_raise # <- Code after this line is no longer executed

Et puis vous pouvez trouver en utilisant catch une exception ici:

try do
  IO.inspect MayRaiseGenServer.maybe_will_raise
catch
  :exit, _ -> IO.puts "there was an error"
end

J'espère que cela clarifiera suffisamment ce que nous recherchons.

26
TheAnh

D'autres réponses couvrent déjà bien l'utilisation de raise contre throw.

Je vais décrire la mécanique de la façon de gérer chaque situation exceptionnelle à l'aide d'un tableau:

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

error(x) est en fait :erlang.error(x).

De plus, rescue et catch/1 (Catch avec 1 argument) ne sont qu'un sucre syntaxique. Les 4 cas ci-dessus peuvent être traités avec 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

Notez l'asymétrie de gestion raise et error avec rescue vs catch/2: x est encapsulé dans %ErlangError Lorsque rescue est utilisé, mais pas avec catch/2.

15
Dimagog

En lisant la réponse de Dimagog, et l'article trouvé sur https://inquisitivedeveloper.com/lwm-elixir-48/ , j'ai vraiment acquis beaucoup de perspicacité sur la question. Je partage juste un exemple pratique personnel,

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 

Le code d'erreur postgresql provient d'une fonction plpgsql dans laquelle je déclenche une erreur, comme suit,

 raise invalid_password using
   message = 'Invalid username or password' , 
   detail = 'A user could not be found that matched the supplied username and password';
1
chava