It is common practice in node.js to return error message as the first argument to a callback function. There are a number of solutions to this problem in pure JS (Promise, Step, seq, etc), but none of them seem to be integrable with ICS. What would be correct solution to handle errors without losing much of readability?
For example:
# 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 # ...
As discussed in Issue #35 of the IcedCoffeeScript repository, there's another technique based on iced-style connectors, which are functions that take as input a callback/deferral, and return another callback/deferral.
Imagine your project has a standard order of arguments to callbacks: the first parameter is always the error, which is null on success. Also, assume further, that you want to leave a function at the first sign of error.
The first step is to make a connector, which I'm calling an "ErrorShortCircuiter" or "ESC":
{make_esc} = require 'iced-error'
Which is implemented like so:
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
To see what this is doing, consider an example of how to use it:
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
This version of my_fn
first makes an ErrorShortCircuiter (or esc
), whose job is twofold: (1) to fire gcb
with the error object; and (2) log a message about where the error happened and what the error was. Obviously you should vary the exact behavior based on your setting. Then, all subsequent calls to library functions with callbacks will be given callbacks generated by defer
as usual, and then run through the esc
connector, which will change the behavior of the callback. The new behavior is to call the gcb
global to the function on an error, and to let the current await
block finish on success. Also, in the succes case, there's no need to deal with a null error object, so only the subsequent slots (like id
, image
, and permitted
) are filled in.
This technique is very powerful and customizable. The key idea is that the callbacks generated by defer
are really continuations and can alter the subsequent control flow of the whole program. And they can do this in a library, so you can get the error behavior you need for many different types of applications, that call upon libraries with different conventions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With