Elixir - 尝试/捕捉vs尝试/救援?

时间:2016-10-27 09:25:13

标签: elixir

背景

Elixir中的try/rescuetry/catch都是错误处理技术。根据介绍指南中的corresponding chapter

  

使用try/rescue构造

可以挽救错误

另一方面,

  

throwcatch保留用于无法检索值的情况,除非使用throwcatch

质疑

我简要了解rescue是出错的。虽然catch适用于任何值。

然而,

  • 我应该何时使用Elixir中的错误处理机制?
  • 它们之间有什么区别?
  • 我应该如何选择一个用于特定用例?
  • '除非使用throwcatch',否则无法检索值的情况究竟是什么?

5 个答案:

答案 0 :(得分:20)

这是一个很好的问题。经过一番研究。

  • 它们之间的细节有何不同?

    José的回答:

  

主要是,您应该使用throw来控制流,并保留raise来解决错误,这些错误会发生在开发人员的错误或特殊情况下。

     

在Elixir中,这种区别相当理论化,但它们很重要   一些语言,如Ruby,使用错误/异常   控制流是昂贵的,因为创建异常对象和   回溯很昂贵。

  • 我应该如何选择一个用于特定用例?

请检查此回答Which situations require throw catch in Elixir

不久:

raise/rescue

考虑使用raise / rescue来明确地处理异常处理(程序员错误,错误环境等一些意外情况)。

throw/catch

在预期失败的地方很有用。 经典的例子是:

最后一个:

  • 究竟是什么'除非使用throw和catch'否则无法检索值?

假设您正在尝试运行由Supervisor监督的流程中的某些代码,但该流程会因意外原因而死亡。

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

MayRaiseGenServerSupervisor监督,出于某种原因引发了错误:

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

好的。希望能够澄清我们正在寻找的东西。

答案 1 :(得分:7)

其他答案已涵盖raisethrow的使用情况。

我将描述如何使用表格处理每种异常情况的机制

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)

除此之外,rescuecatch/1(用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

请注意处理raiseerrorrescuecatch/2的{​​{1}}的不对称性x包含在{{{ 1}}使用%ErlangError但不使用rescue

答案 2 :(得分:1)

通过阅读Dimagog的答案以及在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';

答案 3 :(得分:1)

我喜欢用这个比喻:

您要么catch扔了球,要么rescue来自山上的人。

  • catch-是预期的并用于控制流(例如,类似Java的错误处理)
  • rescue-发生意外错误(例如运行时错误)

答案 4 :(得分:0)

try/catch 必须在已知错误(例如请求验证错误)时使用,而 raise/rescue 必须用于捕获异常(异常是未知或未处理的错误)。

可以结合使用这两者。例如,

def example(conn, params) do 
    try do
      data  = %{}
      types = %{field1: :string, field2: :integer}
  
      changeset =
        {data, types}
        |> cast(params, [:field1, :field2])
        |> validate_required([:field1, :field2])

      if (!changeset.valid?) do
        throw({:ClientError, changeset.errors })
      end
      
      # some logic
      
      raise ArgumentError
    catch
      # client error is caught here
      {:ClientError, error} ->
        IO.inspect(error, label: "client error")
    rescue
      exception ->
        # Argument error is caught here
        IO.inspect(exception, label: "exception") 
    end
  end