【问题标题】:Elixir - try/catch vs try/rescue?Elixir - 尝试/捕捉与尝试/救援?
【发布时间】:2017-03-09 22:02:12
【问题描述】:

背景

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

可以使用try/rescue 构造来挽救错误

另一方面,

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

怀疑

我有一个简单的了解,rescue 是用于错误的。而catch 则适用于任何值。

然而,

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

【问题讨论】:

标签: elixir


【解决方案1】:

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

  • 它们在细节上有什么区别?

    何塞的回答:

主要是,您应该将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

好的。希望能充分说明我们在寻找什么。

【讨论】:

    【解决方案2】:

    其他答案已经涵盖了 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 的不对称性:当使用 rescue 时,x 被包装到 %ErlangError 中,但不是catch/2

    【讨论】:

    • 显示所有案例的表格非常棒。我总是对救援/捕获以及您可以使用 catch/1 或 catch/2 的事实感到困惑。那清除了它。谢谢!
    • 是的,确实是一个很棒且非常简洁的答案。谢谢!
    • 这张表很有帮助。我认为关于这个主题的官方指南的章节非常混乱。重要的一点是,一切都可以归结为catch/2
    • 它帮助我处理数据库中出现的异常。
    【解决方案3】:

    我喜欢用这个比喻:

    你要么是catch 一个投掷的球,要么是rescue 山上的某个人。

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

    【讨论】:

      【解决方案4】:

      通过阅读 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';
      

      【讨论】:

        【解决方案5】:

        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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-04-02
          • 1970-01-01
          • 2010-11-23
          • 2013-08-04
          相关资源
          最近更新 更多