Flow Control
What's your favorite?
mydb.getForum(function(forum) {
mydb.getThread(forum, function(thread) {
mydb.getMessages(thread, function(messages) {
messages.forEach(function(message) {
mydb.getUserInfo(message.author.id, function(info) {
// Oh god when will this nightmare end
});
});
});
});
});
And turning it into less of a boomerang shape. There are three libraries I've run into that do this in a usable way.
Async.js
The first is Async.js, which came on the scene almost immediately after Node.js was released. It helped-- it let you define an array of functions which you could execute using various strategies. Sequentially, in parallel, in while-loop style, etc etc etc. Problem is, you can't combine strategies without nesting async calls, and nesting is what we're trying to avoid!
Step
So the first flow control library I used seriously in production code was Step. I won't duplicate all the examples from the docs here, but just look at how gorgeous this syntax is. Calling this() advances to the next function, passing along its arguments.
Step(
function getThread() {
mydb.getThread(threadId, this);
},
function getAuthors(err, thread) {
if (err) throw err;
var next = this;
thread.messages.forEach(function(message) {
mydb.getUser(message.author.id, next.parallel());
});
},
function processUsers(err) {
if (err) throw err;
var users = Array.prototype.slice.call(arguments);
users.shift();
users.forEach(function(user) {
// Do something
});
},
function catchError(err) {
console.error("Oh no!", err);
}
);Look at how beautiful! The syntax! The easy argument passing! The handling of exceptions! Mixed sequential and parallel processing! I used this for awhile and loved it. Sometimes I still use it today for tasks that don't require anything more complex.
The problem with the above, though, is three-fold. First is this guy:
thread.messages.forEach(function(message) {
mydb.getUser(message.author.id, next.parallel());
});It's innocent enough, but really, we just wrote an entire function so that we could run a *different* function on all the results that were passed back. Wouldn't it be easier just to write one function and tell it to run for each of the results?
Then we have this guy in almost every function you write into a Step:
if (err) throw err;
The up-side is that it lets you handle an error on a per-function basis if you want to, but you're doing it by repeating yourself almost constantly. And Don't Repeat Yourself is the first rule of good programming!
Last but not least:
// Doing this:
thread.messages.forEach(function(message) {
mydb.getUser(message.author.id, next.parallel());
});
// Means the next function needs to do this because all those results
// were passed as arguments instead of an array:
var users = Array.prototype.slice.call(arguments);That one really gets to me. It's just ugly. It's great if you're only parallel processing a couple, fixed number of tasks... but when you have an unknown quantity, this solution isn't great.
There's a parallel grouping feature in Step too which is super cool. So it's a great little library -- it's just not suited to really complex flows. Thankfully, Node.js powerhouse Substack attacked that problem head on, bringing us to...
Seq
Oh my galloping giddiness I love Seq. It's complex, no doubt. You have to read through the docs because, unlike Step, Seq has some complexity under the hood that won't be apparent in a couple examples. But the core idea is this:
- Errors aren't passed on to the next function in the chain like they are with Step. Instead, if an error is returned (via a non-false first argument in the callback), Seq skips down to the first .catch() function. Zero manual error-handling in normal functions.
- Arguments are put on the "stack", which is really just an array. Everything in the "stack" will be sent as individual arguments to the next function in the chain.
- Have a case like the above, where you have a ton of arguments and want them to become an array? Call .unflatten() before the next function. Did your last function return an array and you want to do the opposite? .flatten() it.
- 'seq' is short for Sequential, used to define a function that should process and call its callback before the next is executed. 'par' is short for Parallel.
Armed with the above knowledge, check out this sexy sexy code:
Seq()
.seq(function getThread() {
mydb.getThread(threadId, this);
})
.catch(function(err) {
console.error("Thread not found:", err);
})
.seq(function getMessages(thread) {
this(null, thread.messages);
})
.flatten()
.parEach(5, function getUser(message) {
// The 5 above means we're running these calls in parallel,
// but capped at 5 running at one time. Amazing!
mydb.getUser(message.author.id, this);
})
.unflatten()
.seq(function processUsers(users) {
// Do something
})
.catch(function(err) {
console.error("Unknown error:", err);
});Notice the nesting of absolutely nothing in the above code. Absolutely a breath of fresh air
messagesout of [c]thread[c], or I would have had to nest a callback, and neither of those are great, but all-in-all it's a very cool way to control the flow of what would otherwise be a nightmare of nesting.
So, who uses what? Are there any great ones that I missed?






Cartoon Clouds
Mountains
Sunrise
Clouds
Green Clouds
None














Help