它是在node.js中常见的做法是返回错误消息作为第一个参数的回调函数。 有许多在纯JS(无极,步骤,序列等),但它们都没有这个问题的解决方案似乎是积与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 # ...
我通过样式和编码约定解决这个问题。 而且它拿出所有的时间。 让我们以下面的代码片段,充实了一点点,使我们拥有一个可行的功能。
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]
正如所讨论的问题#35的IcedCoffeeScript库的,还有基于冰式连接器 ,其是作为输入的回调/延迟功能的另一种技术,并返回另一个回调/推迟。
试想一下,你的项目有一个标准订单的参数回调:第一个参数始终是错误,这是成功空。 此外,进一步假设,你想在错误的第一个迹象离开的功能。
第一步是使一个连接器,我打电话的“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
是真正的延续和可以改变整个程序的后续控制流程。 他们可以在图书馆这样做,这样你就可以得到你需要许多不同类型的应用程序,与不同的约定在库调用错误行为。