Simple Callbacks - Executing parallel and dependent tasks using async without the sphagetti

There has been some bloggers complaining lately about the callback pattern and its spaghetti code structure.  Some even compare it to GOTO statements, although that post is less about coffee/javascript.  The beauty of javascript, especially written in coffee-script, is that you can conceptualize parallel and dependent tasks based on code indentation.  Commands executed in the same depth are executed at the same time, while code resting deeper (in the particular case of callback patterns) are deferred until dependent execution is complete.

The following image on the left depicts what I am explaining above.  Blue lines are executed at the same time.  Red lines are dependent on their blue parent's completion before they execute, and so on.

I then take the idea a step further and mix batches of parallel tasks with tasks dependent on the batch to complete.

This is a sample gist of personal work I am doing on a Project Management webApp. The goal of this gist is to show how non dependent tasks can be parallelized and dependent tasks can be run after those parallel dependencies are run. eachTask iterator takes a task object and a callback. It uses the Function prototype method call to pass scope to each parallel task.



_helper = require '_helper'
###
completeIssue is a great example of parallel scripting.
req
- project ObjectId
- milestone ObjectId
- issue ObjectId
Since we know all three ObjectIds related to an Issue from the req
object, we can parallelize a few of the tasks: findIssueAndClose, socket.emit response,
getMilestoneTallyCompletedIssues, getProject info, getIssueAttachments
each pushed task has a cb which runs along side the cb of async.each iterator. each task
cb adds a particular result to the scope object. When all iterators are complete, the parallel
tasks end and execute the last few callback statements: findAccount of the issue owner,
and sendIssueCompletedMail
###
completeIssue = (req)->
async = require 'async'
# Add other necessary references to this object
# Scope dependencies are hidden behind _helper classes
# to simplify this example
scope =
req : req
log = console.log
parallelTasks = []
parallelTasks.push
fn : _helper.findIssueAndClose
opts : { _id: req.issue }
cb : (err, issue)->
if err
log err
scope.issue = issue
parallelTasks.push
fn : _helper.getMilestoneTallyCompletedIssues
opts : {_id: req.milestone}
cb : (err, milestone)->
if err
log err
scope.milestone = milestone
parallelTasks.push
fn : _helper.getProject
opts : { _id: req.project }
cb : (err, project)->
if err
log err
scope.project = project
parallelTasks.push
fn : _helper.getIssueAttachments
opts : { issue: req.issue }
cb : (err, mailAttachments)->
if err
log err
scope.mailAttachments = mailAttachments
parallelTasks.push
fn : _helper.getComments
opts : { issue: req.issue }
cb : (err, comments)->
if err
log err
scope.comments = comments
eachTask = (task, cb)->
task.fn.call scope, task.opts, (err, res)->
task.cb(err, res)
cb()
async.each parallelTasks, eachTask, ()->
_helper.findAccount.call scope, { _id: scope.issue.owner }, (err, account)->
if err
log err
scope.account = account
if scope.project && scope.milestone && scope.issue
_helper.sendIssueCompletedMail.call scope, (err)->
if err
log err
return @
module.exports = completeIssue