The lib that makes the callback hell less scary
September 04, 2016
Async lets you build sophisticated flows, it has great capabilities for retries and timeouts, and even wonders like async.queue or async.whilsts. Aync makes the code much more readable, and allows you to unleash the full power of Node.js - efficient asynchronous programming.
It lets you streamline error handling and passing.
In Node.js code you will see a lot of repetition,
one of the most common repetitions is the if(err)
block.
getA(params, (err, a)=>{
if(err)return callback(err);
return getB(params, (err, b)=>{
if(err)return callback(err);
//etc.
})
});
Async usually breaks the flow if any error occurs - it allows you to get rid of the repetition, and handle errors in one place - the final callback.
My callback style: I name first level of async
callbacks simply cb
and the main callback
,
if I need to introduce more levels of callbacks -
I name them accoringly to the async method they belong to (eachCallback
will belong to async.each
)
async.parallel(
{
a: (cb)=>getA(params, cb),
b: (cb)=>getB(params, cb),
c: (cb)=>getC(params, cb),
d: (cb)=>getD(params, cb)
//etc.
},
(err, results)=>{
if(err) return console.error(err);
console.log( (results.a + results.b) * (results.c + results.d) )
}
);
Combining asynchronous and synchronous with async is a pleasure!
I find it more readable to write validation function like one below in synchronous manner.
function validateData(data) {
if(!data.name) throw new Error('Name is missing');
if(data.age < 0) throw new Error('Age cannot be negative');
if(isNan(data.height)) throw new Error('Height must be a number');
}
I can safely plugin this function to an asynchronous flow using asyncify
.
async.autoInject(
{
rawData: (cb)=>getData(params, cb),
validate: (rawData, cb)=> async.asyncify(validateData)(rawData, cb),
save: (rawData, validate, cb)=> db.save(rawData, cb)
},
callback
)
This way if any error is thrown inside validateData
function
it will be passed to the callback
and break the autoInject
flow.
There is a hidden benefit to it - if rawData
passed to validateData
is undefined
a TypeError: Cannot read property 'name' of undefined
is throw
asyncify
will also catch this error and pass it to the main callback
JSON.parse
is a good subject for async.asyncify
If you are not using asyncify
for your synchronous code remember to use setImmediate(cb)
,
otherwise you may blow up the stack.
Async goes well with readability and functional programming
Assignments and temporary variables can lead to bugs.
Async lets you write functions that begin with a return
, without assignments, in more functional style.
There is a lot of possible control flows with async - almost certainly you will find one that matches your needs, and allows to avoid assignments. It is easier to reason about code that uses async heavily.
In following example async is doing all of the pipelining.
function notifyFriends(id, message, callback){
return async.waterfall(
[
(cb)=>getUser(id, cb),
(user, cb)=>getFriends(user.friends, cb),
(friends, cb)=>{
return async.eachSeries(friends,
(friend, eachCallback)=>sendMessage(friend, message, eachCallback),
cb
);
},
(cb)=>saveMessage(message, cb)
],
callback
);
}