Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly handle errors with IcedCoffeeScript?

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  # ...
like image 668
Andrew Avatar asked Jan 14 '13 16:01

Andrew


1 Answers

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.

like image 81
Max Krohn Avatar answered Sep 30 '22 19:09

Max Krohn