I really like how the await/defer structure helps writing async code. Lets go even further!
Currently, async error handling in node.js is a pain.
# Sample Express code with basic error handling
app.get '/', (req, res, next) ->
db.readUsersList (err, users) ->
if err? then return next(err)
db.readArticles (err, articles) ->
if err? then return next(err)
res.render 'index', {users, articles}
# Sample Async code with basic error handling
readAndProcessFile = (filename, callback) ->
fs.readFile filename, 'utf8', (err, text) ->
if err? then return callback(err) # Docs suggest to throw err, but its meaningless in async code.
process text, (err, result) ->
if err? then return callback(err)
callback null, result
As you can see, there are lots of if err? then return callback(err)
everywhere, that's not good. Note that this is a very frequent pattern in asynchronous code.
I usually write a helper function to make the code a lot cleaner, something along the lines of:
# Error-handling helper. Use like this:
# asyncFunction params, errTo next, (results) ->
errTo = (next, callback) ->
(err, params...) ->
if err? return next(err)
callback params...
# Sample Express code
app.get '/', (req, res, next) ->
db.readUsersList errTo next, (users) -> # Note no 'err' parameter.
db.readArticles errTo next, (articles) ->
res.render 'index', {users, articles}
So what can we do in IcedCoffeeScript? Lets see what can be done right now.
# Current way of error handling: same as in vanilla coffeescript, although a little cleaner because of lack of indentation.
app.get '/', (req, res, next) ->
await db.readUsersList defer(err, users)
if err? then return next(err)
await db.readArticles defer(err, articles)
if err? then return next(err)
res.render 'index', {users, articles}
# Harder case: parallel fetching.
# I even dont know how to handle individual errors here.
app.get '/', (req, res, next) ->
await
db.readUsersList defer(err, users)
db.readArticles defer(err, articles)
if err? then return next(err) # Not effective: waits for all deferreds to be fulfilled, also one error might overwrite the other.
res.render 'index', {users, articles}
What's really lacking here is the semantics of try/catch clause, but with asynchronous twist and on a function level only (dont need to go up the stack). This is clearly hard to do in vanilla CoffeeScript, but I think might be doable in Iced.
Requirements:
- The syntax should be easy to understand. Use familiar constructs.
- Opt-in behavior. Should be easy to mix with the usual way of error handling.
- Should help programmer in dealing with serial and parallel flows. I.e. stop iteration when error is first encountered.
- Should look nice both in one-line form and block form of 'await'.
# Variant 1. Similar to my errTo helper
readAndProcess = (filename, cb) ->
await fs.readFile filename, 'utf8', defer(~>cb, text) # We can use any symbol instead of ~>.
await
asyncOperation1 text, defer(~>cb, result1)
asyncOperation2 text, defer(~>cb, result2)
cb null, result1, result2
# Variant 2. Try/catch inspired
readAndProcess = (filename, cb) ->
await fs.readFile filename, 'utf8', defer text catch cb
await
asyncOperation1 text, defer(result1)
asyncOperation2 text, defer(result2)
catch cb
# Also, we can provide an error handler in-place:
await
asyncOperation1 text, defer(result1)
catch (err) ->
console.log err
# Todo: do we allow return to the normal workflow?
cb null, result1, result2
# Variant 3. Auto_cb automatically does this for the whole function
readAndProcess = (filename, auto_cb) ->
await fs.readFile filename, 'utf8', defer(text)
await
asyncOperation1 text, defer(result1)
asyncOperation2 text, defer(result2)
return {result1, result2}
What do you think? Is it doable for more generic case (ifs, whiles, etc.)?