Эликсир - попробовать/поймать против попытки/спасения?

Фон

Оба метода try/rescue и try/catch - это методы обработки ошибок в Elixir. В соответствии с соответствующей главой в руководстве по вводу.

Ошибки могут быть спасены с помощью конструкции try/rescue

С другой стороны,

throw и catch зарезервированы для ситуаций, когда невозможно получить значение, если только с помощью throw и catch.

Сомнения

Я кратко понимаю, что rescue для ошибок. Пока catch для любого значения.

Однако

  • Когда я должен использовать механизмы обработки ошибок в Elixir?
  • Каковы различия между ними в деталях?
  • Как выбрать один для использования в конкретном случае использования?
  • В чем именно "ситуации, когда невозможно получить значение, если только с помощью throw и catch '?

Ответ 1

Это хороший вопрос. После исследования немного.

  • Каковы различия между ними в деталях?

    Ответ Жозе:

В основном, вы должны использовать throw для потока управления и зарезервировать raise для ошибок, которые происходят при ошибках разработчиков или при исключительных обстоятельствах.

В Эликсире это различие довольно теоретично, но они имеют значение в некоторые языки, такие как Ruby, где используются ошибки/исключения для поток управления дорог, потому что создается объект исключения и backtrace стоит дорого.

  • Как выбрать один для использования в конкретном случае использования?

Пожалуйста, проверьте этот ответ Какие ситуации требуют броска в Elixir

Коротко:

raise/rescue

Считайте raise/rescue явно о обработке исключений (некоторые непредвиденные ситуации, такие как ошибки программиста, неправильная среда и т.д.).

throw/catch

Полезен в тех местах, где вы ожидали сбоев. Классическими примерами являются:

Последний:

  • В чем именно "ситуации, когда невозможно получить значение, если только с помощью throw and catch"?

Скажем, вы пытаетесь запустить какой-то код из процесса, который контролируется Supervisor, но процесс умирает по непредвиденной причине.

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

MayRaiseGenServer контролируется Supervisor и по какой-то причине возникает ошибка:

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

И тогда вы можете придумать здесь исключение catch:

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

Ok.Hope, которые достаточно проясняют, что мы ищем.

Ответ 2

Другие ответы уже хорошо охватывают использование raise и throw.

Я опишу механизм обработки каждой исключительной ситуации с помощью таблицы:

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) на самом деле :erlang.error(x).

Кроме того, rescue и catch/1 (catch с 1 аргументом) являются просто синтаксическим сахаром. Все 4 приведенных выше случая могут быть обработаны с помощью 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

Обратите внимание на асимметрию обработки raise и error с rescue против catch/2: x включается в %ErlangError при использовании rescue, но не с catch/2 ,

Ответ 3

Прочитав ответ димагога и статью, найденную на https://inquisitivedeveloper.com/lwm-elixir-48/, я действительно многое понял по этому вопросу. Я просто делюсь личным практическим примером,

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 

Код ошибки postgresql происходит из функции plpgsql, в которой я выдаю сообщение об ошибке следующим образом:

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