你能告诉我什么时候使用throw
,exit
和error
?
1> catch throw ({aaa}).
{aaa}
2> catch exit ({aaa}).
{'EXIT',{aaa}}
3> catch gen_server:call(aaa,{aaa}).
{'EXIT',{noproc,{gen_server,call,[aaa,{aaa}]}}}
4> catch exit("jaj")
{'EXIT',"jaj"}
你能告诉我什么时候使用throw
,exit
和error
?
1> catch throw ({aaa}).
{aaa}
2> catch exit ({aaa}).
{'EXIT',{aaa}}
3> catch gen_server:call(aaa,{aaa}).
{'EXIT',{noproc,{gen_server,call,[aaa,{aaa}]}}}
4> catch exit("jaj")
{'EXIT',"jaj"}
有3 个类可以用try ... catch
:throw
和.error
exit
throw
使用生成throw/1
并且旨在用于非本地返回并且不会生成错误,除非它没有被捕获(当您收到nocatch
错误时)。
error
当系统检测到错误时生成。您可以使用显式生成错误error/1
。系统还在生成的错误值中包含堆栈跟踪,例如{badarg,[...]}
.
exit
是使用生成exit/1
的,旨在表明该进程将终止。
和之间的区别error/1
并exit/1
没有那么大,更多的是关于错误生成的堆栈跟踪增强的意图。
它们之间的区别实际上在执行时更明显catch ...
:当throw/1
使用时,则catch
返回抛出的值,正如非本地返回所期望的那样;当error/1
使用 an 时,catch
返回包含堆栈跟踪的{'EXIT',Reason}
位置;Reason
while fromexit/1
catch
也返回{'EXIT',Reason}
,但Reason
仅包含实际退出原因。try ... catch
看起来它等同于它们,但它们/曾经非常不同。
[更新]
Robert Virding 指出,我忽略了throw和error之间的重要区别。此编辑仅用于记录!
throw error
将用于throw
在其他语言中使用的地方。您的代码检测到正在运行的进程中的错误,这表示异常,带有error/1
. 同一个进程捕获它(可能在堆栈中更高),并且错误将在同一个进程中处理。error
总是带来一个堆栈跟踪。
throw
不用于发出错误信号,而只是从深度嵌套的函数中返回一个值。由于它展开堆栈,调用throw
将抛出的值返回到它被捕获的位置。与 的情况一样error
,我们正在捕获被抛出的东西,只有被抛出的不是错误,而只是一个向上传递到堆栈的值。这就是为什么 throw 不会带来堆栈跟踪。
作为一个人为的例子,如果我们想exists
为列表实现一个函数,(类似于什么list:any
)并且作为一个练习而不自己进行递归,并且使用 just list:foreach
,那么throw
可以在这里使用:
exists(P, List) ->
F = fun(X) ->
case P(X) of
true -> throw(true);
Whatever -> Whatever
end
end,
try lists:foreach(F, List) of
ok -> false
catch
true -> true
end.
抛出但未捕获的值被视为error
:nocatch
将生成异常。
EXIT 将在进程“放弃”时发出信号。父进程处理 EXIT,而子进程只是死掉。这就是 Erlang 让它崩溃的哲学。
所以exit/1
's EXIT 不是在同一个进程中被捕获,而是留给父进程。error/1
的错误是进程本地的 - 即,发生了什么以及进程本身如何处理它的问题;throw/1
用于跨堆栈的控制流。
[更新]
exit/2
- 调用了一个Pid
将 EXIT 发送到的进程。
exit/1
表示父进程。我是 Erlang 的新手,但以下是我对这些东西是什么、它们的区别、它们的用途等的看法:
throw
:应该在本地处理的条件(即在当前进程中)。例如,调用者正在寻找集合中的元素,但不知道集合中是否真的包含这样的元素;然后,如果这样的元素不存在,被调用者可以抛出,并且调用者使用try[/of]/catch
. 如果调用者忽略了这样做,那么这将变成一个nocatch
error
(解释如下)。
exit
: 当前进程完成。例如,它刚刚完成(在这种情况下,您将传递normal
,它被视为与返回的原始函数相同),或者它的操作被取消(例如,它通常无限循环但刚刚收到一条shut_down
消息)。
error
: 进程做了一些事情和/或达到了程序员没有考虑到的状态(例如 1/0),认为不可能(例如case ... of
遇到一个不匹配任何情况的值),或者某些先决条件不满足(例如输入是非空的)。在这种情况下,本地恢复没有意义。因此,既不合适throw
也exit
不合适。由于这是出乎意料的,堆栈跟踪是原因的一部分。
如您所见,上面的列表是按升级顺序排列的:
throw
适用于调用者应该处理的正常情况。即处理发生在当前进程中。
exit
也是理智的,但应该仅仅因为过程完成就结束当前过程。
error
疯了。发生了无法合理恢复的事情(通常是错误?),并且本地恢复不合适。
与其他语言相比:
throw
类似于 Java 中使用检查异常的方式。然而,error
以更类似于未经检查的异常的方式使用。已检查异常是您希望调用者处理的异常。Java 要求您将调用包装起来try/catch
或声明您的方法throws
出现此类异常。然而,未经检查的异常通常会传播到最外层的调用者。
exit
在 Java、C++、Python、JavaScript、Ruby 等更“传统”的语言中没有一个很好的类比。exit
模糊地像一个 uber- return
:你可以从函数的中间返回,而不是在最后返回,除了你不要只从当前函数返回,而是从它们全部返回。
exit
例子
serve_good_times() ->
receive
{top_of_the_mornin, Sender} ->
Sender ! and_the_rest_of_the_day_to_yourself;
{you_suck, Sender} ->
Sender ! take_a_chill_pill;
% More cases...
shut_down ->
exit(normal)
end,
serve_good_times()
end
由于serve_good_times
几乎在所有消息之后都会调用自身,因此程序员决定我们不想在每个接收情况下都重复该调用。因此,她在接听电话后打了那个电话。但是,如果serve_good_times
决定停止调用自己呢?这就是exit
救援的地方。传递normal
给exit
会导致进程终止,就像最后一个函数调用已经返回一样。
因此,exit
在通用库中调用通常是不合适的,例如lists
. 该过程是否应该结束与图书馆无关。这应该由应用程序代码决定。
异常exit
怎么办?
如果另一个进程(“远程”进程)链接到调用exit
(并且process_flag(trap_exit, true)
未被调用)的“本地”进程,这很重要:就像最后一个函数返回一样,exit(normal)
不会导致远程进程退出。但是如果本地进程进行exit(herp_derp)
调用,那么远程进程也会以Reason=herp_derp
. 当然,如果远程进程链接到更多进程,它们也会获得退出信号Reason=herp_derp
。因此,normal
不退出会导致连锁反应。
让我们看一下实际情况:
1> self().
<0.32.0>
2> spawn_link(fun() -> exit(normal) end).
<0.35.0>
3> self().
<0.32.0>
4>
4>
4> spawn_link(fun() -> exit(abnormal) end).
** exception exit: abnormal
5> self().
<0.39.0>
6>
我们生成的第一个进程并没有导致 shell 退出(我们可以看出,因为self
在 之前和之后返回了相同的 pid spawn_link
)。但是第二个进程确实导致 shell 退出(并且系统用新进程替换了 shell 进程)。
当然,如果远程进程使用,process_flag(trap_exit, true)
那么它只会收到一条消息,而不管本地进程是否通过normal
或其他exit
。设置此标志会停止连锁反应。
6> process_flag(trap_exit, true).
false
7> spawn_link(fun() -> exit(normal) end).
<0.43.0>
8> self().
<0.39.0>
9> flush().
Shell got {'EXIT',<0.43.0>,normal}
ok
10>
10>
10> spawn_link(fun() -> exit(abnormal) end).
<0.47.0>
11> self().
<0.39.0>
12> flush().
Shell got {'EXIT',<0.47.0>,abnormal}
回想一下,我说过它exit(normal)
被视为原始函数返回:
13> spawn_link(fun() -> ok end).
<0.51.0>
14> flush().
Shell got {'EXIT',<0.51.0>,normal}
ok
15> self().
<0.39.0>
你知道什么:和exit(normal)
被叫时发生了同样的事情。精彩的!