抛出异常
学习如何在 Elixir 中处理和实现异常。
Elixir(如同 Erlang)认为,错误通常应导致发生错误的进程终止。一个典型的 Elixir 应用程序设计包含大量进程,这意味着错误的影响会被限制在局部范围内。监督者会检测到失败的进程,并在该层级处理重启。
因此,在 Elixir 程序中我们很少看到异常处理代码。异常会被抛出,但我们很少去捕获它们。
仅在真正异常的情况下使用异常——即那些本不该发生的情况。
实现
我们可以使用 raise 函数来抛出异常。
最简单的方式是传入一个字符串,它会生成类型为 RuntimeError 的异常。
1iex> raise "Giving up" 2** (RuntimeError) Giving up 3 erl_eval.erl:572: :erl_eval.do_apply/6
我们也可以指定异常类型以及一些可选字段。所有异常至少都实现 message 字段。
1iex> raise RuntimeError 2** (RuntimeError) runtime error 3erl_eval.erl:572: :erl_eval.do_apply/6 4 5iex> raise RuntimeError, message: "override message" 6** (RuntimeError) override message 7 erl_eval.erl:572: :erl_eval.do_apply/6
我们可以使用 try 函数来捕获异常。它接受一段要执行的代码块,以及可选的 rescue、catch 和 after 子句。
rescue 和 catch 子句的写法类似于 case 语句的主体。它们接受模式和匹配成功时要执行的代码。模式匹配的目标是被抛出的异常。
下面是一个异常处理的实际示例。我们定义了一个模块,其中包含公共函数 start。根据参数值的不同,它会调用不同的辅助函数。当参数为 0 时,运行正常;当参数为 1、2 或 3 时,会触发虚拟机抛出错误,我们捕获并报告这些错误。
我们定义了三种不同的异常模式:
- 第一个匹配
FunctionClauseError或RuntimeError这两种异常之一。 - 第二个匹配
ArithmeticError,并将异常值存入变量error。 - 最后一个子句捕获任何其他异常,并将其存入变量
other_error。
我们还添加了一个 after 子句。无论是否抛出异常,这个代码块都会在 try 执行结束时始终运行。
最后,我们来看 ArithmeticError 的处理方式。除了报告错误外,我们还调用了 reraise。这会重新抛出当前异常,但允许我们添加一条新消息。同时,我们还将原始异常抛出时的堆栈跟踪一并传递过去。
1iex> c("exception.ex") 2[Boom] 3 4iex> Boom.start 1 5About to divide by zero 6%ArithmeticError{} 7Uh-oh! Arithmetic error 8DONE! 9** (RuntimeError) too late, we're doomed 10 exception.ex:26: Boom.raise_error/1 11 exception.ex:5: Boom.start/1 12 13iex> Boom.start 2 14About to call a function that doesn't exist no function match or runtime error 15DONE! 16:ok 17 18iex> Boom.start 3 19About to try creating a directory with no permission 20Disaster! %File.Error{action: "make directory", path: "/not_allowed", 21 reason: :eacces} 22DONE! 23:ok
运行上述代码片段中的命令以执行以下代码:
No configuration found for ID: e0f0e91c47b7403749c1cc78d287599d0c1b14396f45558b8021e5a93518c24c