node.js中的常见做法是将错误消息作为回调函数的第一个参数返回。纯JS(Promise,Step,seq等)中有许多解决这个问题的方法,但它们似乎都不能与ICS集成。在不损失可读性的情况下处理错误的正确解决方案是什么?
例如:
# makes code hard to read and encourage duplication
await socket.get 'image id', defer err, id
if err # ...
await Image.findById id, defer err, image
if err # ...
await check_permissions user, image, defer err, permitted
if err # ...
# will only handle the last error
await
socket.get 'image id', defer err, id
Image.findById id, defer err, image
check_permissions user, image, defer err, permitted
if err # ...
# ugly, makes code more rigid
# no way to prevent execution of commands if the first one failed
await
socket.get 'image id', defer err1, id
Image.findById id, defer err2, image
check_permissions user, image, defer err3, permitted
if err1 || err2 || err3 # ...
答案 0 :(得分:12)
我通过样式和编码约定来解决这个问题。它确实一直出现。让我们把你的片段放在下面,充实一点,以便我们有一个可行的功能。
my_fn = (cb) ->
await socket.get 'image id', defer err, id
if err then return cb err, null
await Image.findById id, defer err, image
if err then return cb err, null
await check_permissions user, image, defer err, permitted
if err then return cb err, null
cb err, image
你是完全正确的,这很丑陋,因为你在许多地方短路代码,你需要记住每次返回时都要调用cb。
您提供的其他代码段会产生错误的结果,因为它们会引入需要序列化的并行性。
我个人的ICS编码约定是:(1)只从函数返回一次(控件从结尾掉下来); (2)尝试在同一级别的缩进处理错误。以我喜欢的方式重写你所拥有的东西:
my_fn = (cb) ->
await socket.get 'image id', defer err, id
await Image.findById id, defer err, image unless err?
await check_permissions user, image, defer err, permitted unless err?
cb err, image
如果socket.get调用出错,则需要检查错误两次,并且两次都会失败。我不认为这是世界末日,因为它使代码更清晰。
或者,你可以这样做:
my_fn = (autocb) ->
await socket.get 'image id', defer err, id
if err then return [ err, null ]
await Image.findById id, defer err, image
if err then return [ err, null ]
await check_permissions user, image, defer err, permitted
return [ err, image ]
如果您使用的autocb不是我最喜欢的ICS功能,那么无论何时返回/短路功能,编译器都会为您调用autocb。我觉得这种结构更容易出错。例如,想象一下你需要在函数开始时获取一个锁,现在你需要释放它n次。其他人可能不同意。
另一个注释,在评论中指出。 autocb
的作用类似return
,因为它只接受一个值。如果要在此示例中返回多个值,则需要返回数组或字典。 defer
会解构分配以帮助您:
await my_fn defer [err, image]
答案 1 :(得分:4)
正如IcedCoffeeScript存储库的Issue #35中所讨论的,还有另一种基于iced风格的连接器的技术,这些函数将回调/延迟作为输入,并返回另一个回调/推迟。
想象一下,你的项目有一个标准的回调参数顺序:第一个参数总是错误,成功时为null。另外,假设您希望在出现错误的第一个迹象时保留函数。
第一步是建立一个连接器,我称之为“ErrorShortCircuiter”或“ESC”:
{make_esc} = require 'iced-error'
这样实现:
make_esc = (gcb, desc) -> (lcb) ->
(err, args...) ->
if not err? then lcb args...
else if not gcb.__esc
gcb.__esc = true
log.error "In #{desc}: #{err}"
gcb err
要了解这是做什么,请考虑如何使用它的示例:
my_fn = (gcb) ->
esc = make_esc gcb, "my_fn"
await socket.get 'image id', esc defer id
await Image.findById id, esc defer image
await check_permissions user, image, esc defer permitted
gcb null, image
此版本的my_fn
首先生成一个ErrorShortCircuiter(或esc
),其作用有两个:(1)使用错误对象触发gcb
; (2)记录有关错误发生位置和错误的信息。显然,您应该根据您的设置改变确切的行为。然后,所有后续调用带有回调的库函数将像往常一样被defer
生成回调,然后通过esc
连接器运行,这将改变回调的行为。新行为是在错误上调用gcb
全局函数,并让当前await
块成功完成。此外,在成功案例中,不需要处理空错误对象,因此只填充后续插槽(如id
,image
和permitted
)。
这种技术非常强大且可定制。关键的想法是defer
生成的回调实际上是连续的,可以改变整个程序的后续控制流。他们可以在库中执行此操作,因此您可以获得许多不同类型的应用程序所需的错误行为,这些应用程序会调用具有不同约定的库。