TL; DR: Use fibers to escape from callback hell!
As someone whose only experience with asynchronous functions was jQuery’s $.ajax
I was in for a treat when I started working on a project using node.js. Functions that took functions as arguments? “That’s okay,” I told myself, “I’ve used functions like map
and filter
.” Little did I know what a mess this could quickly turn into!
The first few weeks weren’t that bad, it was still a new toy that I was playing with! I was then faced with a task where I had to make a database call only if a certain condition was met. Straightforward right? Nope! If things were synchronous you’d just do this:
if (studentName === undefined) {
studentName = getDefaultName()
}
student = getStudent(studentName)
But getDefaultName
is an asynchronous function! So I ended up doing this:
getDefaultName(studentName, function(data) {
getStudent(data)
}
And..
function getDefaultName(studentName, callback) {
if (studentName) {
callback(studentName)
}
actualDbCall(callback)
}
Throw in some error checking and repeat this a few times to end up with the best piece of code ever!
The next issue that I faced was when I no longer had to make some existing asynchronous calls, and had to make some new asynchronous calls! Enter promises
. I could add and remove function calls without messing up everything else. Optional function calls were still…annoying!
A wildly popular question and its many duplicates on Stack Overflow didn’t have a solution to handle this. At this point I started wondering if I was doing something so wrong that I was the only one facing this issue!
After looking at quite a few libraries like async I stumbled upon fibers. It had yield
and run
. Exactly what I needed: stop execution till some data is available and continue once it was available. I could now write the above function as:
if (studentName === undefined) {
studentName = getDefaultName()
}
student = getStudent(studentName)
Just like the synchronous version! :) getDefaultName is defined as:
function getDefaultName(container) {
var current = Fiber.current
var result
actualDbCall(function(name) {
result = name
current.run()
})
Fiber.yield()
return result
}
There hasn’t been any noticeable change in performance by doing this but the code is so much more readable and modifiable after fibers came in. It’s a good option to have till async-await lands in node.js.