Elixir - 错误处理

Elixir 具有三种错误机制:错误、抛出和退出。 让我们详细探讨每个机制。

错误(error)

当代码中发生异常情况时,将使用错误(或异常)。 可以通过尝试将数字添加到字符串中来检索示例错误 −

IO.puts(1 + "Hello")

当运行上面的程序时,会产生以下错误 −

** (ArithmeticError) bad argument in arithmetic expression
   :erlang.+(1, "Hello")

这是一个示例内置错误。

引发错误

我们可以使用 raise 函数引发错误。 让我们考虑一个例子来理解同样的内容 −

#运行时错误,只有一条消息
raise "oops"  # ** (RuntimeError) oops

可以通过 raise/2 传递错误名称和关键字参数列表来引发其他错误

#带有消息的其他错误类型
raise ArgumentError, message: "invalid argument foo"

您还可以定义自己的错误并提出这些错误。 考虑下面的例子 −

defmodule MyError do
   defexception message: "default message"
end

raise MyError  # 使用默认消息引发错误
raise MyError, message: "custom message"  # 使用自定义消息引发错误

修复错误

我们不希望我们的程序突然退出,但需要仔细处理错误。 为此,我们使用错误处理。 我们使用 try/rescue 结构来修复错误。 让我们考虑以下示例来理解相同的内容 −

err = try do
   raise "oops"
rescue
   e in RuntimeError -> e
end

IO.puts(err.message)

当上面的程序运行时,会产生以下结果 −

oops

我们已经使用模式匹配处理了修复语句中的错误。 如果我们对错误没有任何用途,只是想将其用于识别目的,我们也可以使用以下形式−

err = try do
   1 + "Hello"
rescue
   RuntimeError -> "You've got a runtime error!"
   ArithmeticError -> "You've got a Argument error!"
end

IO.puts(err)

运行上面的程序时,会产生以下结果 −

You've got a Argument error!

注意 +减; Elixir 标准库中的大多数函数都会实现两次,一次返回元组,另一次引发错误。 例如,File.readFile.read! 函数。 如果文件读取成功,第一个返回一个元组,如果遇到错误,则使用该元组给出错误的原因。 如果遇到错误,第二个会引发错误。

如果我们使用第一个函数方法,那么我们需要使用 case 来模式匹配错误并据此采取行动。 对于第二种情况,我们对容易出错的代码使用 tryrescue 方法并相应地处理错误。

抛出(throw)

在 Elixir 中,可以抛出一个值,然后再捕获它。 Throw 和 Catch 保留用于除非使用 throw 和 catch 否则无法检索值的情况。

除了与库交互之外,这些实例在实践中并不常见。 例如,现在假设 Enum 模块没有提供任何用于查找值的 API,并且我们需要在数字列表中查找 13 的第一个倍数 −

val = try do
   Enum.each 20..100, fn(x) ->
      if rem(x, 13) == 0, do: throw(x)
   end
   "Got nothing"
catch
   x -> "Got #{x}"
end

IO.puts(val)

当上面的程序运行时,会产生以下结果 −

Got 26

退出(exit)

当进程因"自然原因"(例如未处理的异常)而终止时,它会发送退出信号。 进程也可以通过显式发送退出信号来终止。 让我们考虑下面的例子 −

spawn_link fn -> exit(1) end

在上面的示例中,链接的进程通过发送值为 1 的退出信号而终止。请注意,退出也可以使用 try/catch 来"捕获"。 例如 −

val = try do
   exit "I am exiting"
catch
   :exit, _ -> "not really"
end

IO.puts(val)

当上面的程序运行时,会产生以下结果 −

not really

after

有时,有必要确保在执行某些可能引发错误的操作后清理资源。 try/after 结构允许您做到这一点。 例如,我们可以打开一个文件并使用 after 子句将其关闭——即使出现问题。

{:ok, file} = File.open "sample", [:utf8, :write]
try do
   IO.write file, "olá"
   raise "oops, something went wrong"
after
   File.close(file)
end

当我们运行这个程序时,它会给我们一个错误。 但是 after 语句将确保文件描述符在发生任何此类事件时关闭。