440 likes | 642 Views
Avoiding Callback Hell with Async.js. www.codehenge.net. C. Aaron Cois, Ph.D. Sup. www.codehenge.net. @ aaroncois. github.com / cacois. So, JavaScript?. What’s cool?. Robust event model Asynchronous programming Client-side and Server-side. What’s cool?. Robust event model
E N D
Avoiding Callback Hell with Async.js www.codehenge.net C. Aaron Cois, Ph.D.
Sup www.codehenge.net @aaroncois github.com/cacois
What’s cool? • Robust event model • Asynchronous programming • Client-side and Server-side
What’s cool? • Robust event model • Asynchronous programming • Client-side and Server-side
Callbacks JavaScript uses callback functions to handle asynchronous control flow
Anatomy of a Callback fs= require('fs'); fs.readFile('f1.txt','utf8',function(err,data){ if(err){ returnconsole.log(err); } console.log(data); });
Anatomy of a Callback fs= require('fs'); fs.readFile('f1.txt','utf8',function(err,data){ if(err){ returnconsole.log(err); } console.log(data); }); Anonymous, inline callback
Anatomy of a Callback fs= require('fs'); fs.readFile('f1.txt','utf8', function(err,data){ if(err){ returnconsole.log(err); } console.log(data); } ); Equivalent formatting
Callback Hell When working with callbacks, nesting can get quite out of hand…
Callback Hell func1(param,function(err,res){ func2(param,function(err, res){ func3(param,function(err, res){ func4(param,function(err, res){ func5(param,function(err, res){ func6(param,function(err, res){ func7(param,function(err, res){ func8(param,function(err, res){ func9(param,function(err, res){ // Do something… }); }); }); }); }); }); }); }); });
Callback Hell func1(param,function(err,res){ func2(param,function(err, res){ func3(param,function(err, res){ func4(param,function(err, res){ func5(param,function(err, res){ func6(param,function(err, res){ func7(param,function(err, res){ func8(param,function(err, res){ func9(param,function(err, res){ // Do something… }); }); }); }); }); }); }); }); });
Best case, this is linear func1 func2 func3 . . . func9
But it can branch func1 func2 func2 func3 func3 func3 func3 . . . . . . . . . . . . func9 func9 func9 func9
But it can branch func1 … func2(param,function(err, results){ _.each(results, func3(param,function(err, res){ func4(param,function(err, res){ … }); } }); }); func2 func2 func3 func3 func3 func3 . . . . . . . . . . . . func9 func9 func9 func9
Some specific challenges • When branching, we can’t know the order of these function calls • If we want parallel execution, we have to do some real gymnastics to get return data back together • Also, scoping becomes a challenge
A more ‘real’ example vardb= require('somedatabaseprovider'); //get recent posts http.get('/recentposts',function(req, res){ // open database connection db.openConnection('host',creds,function(err, conn){ res.param['posts'].forEach(post){ conn.query('select * from users where id='+post['user'],function(err,users){ conn.close(); res.send(users[0]); }); } }); });
A more ‘real’ example vardb= require('somedatabaseprovider'); //get recent posts http.get('/recentposts',function(req, res){ // open database connection db.openConnection('host',creds,function(err, conn){ res.param['posts'].forEach(post){ conn.query('select * from users where id='+post['user'],function(err,users){ conn.close(); res.send(users[0]); }); } }); });
Solutions • You can make this easier to read by separating anonymous functions • Passing function references instead of anonymous functions helps even more
Inline callback fs= require('fs'); fs.readFile('f1.txt','utf8',function(err,data){ if(err){ returnconsole.log(err); } console.log(data); });
Separate Callback fs= require('fs'); callback = function(err,data){ if(err){ returnconsole.log(err); } console.log(data); } fs.readFile('f1.txt','utf8',callback);
Can turn this: vardb= require('somedatabaseprovider'); http.get('/recentposts',function(req,res){ db.openConnection('host', creds, function(err, conn){ res.param['posts'].forEach(post){ conn.query('select * from users where id='+ post['user'],function(err,results){ conn.close(); res.send(results[0]); }); } }); });
…into this vardb= require('somedatabaseprovider'); http.get('/recentposts',afterRecentPosts); functionafterRecentPosts(req, res){ db.openConnection('host',creds,function(err, conn){ afterDBConnected(res, conn); }); } functionafterDBConnected(err, conn){ res.param['posts'].forEach(post){ conn.query('select * from users where id='+post['user'],afterQuery); } } functionafterQuery(err, results){ conn.close(); res.send(results[0]); }
Good start! • Callback function separation is a nice aesthetic fix • The code is more readable, and thus more maintainable • But it doesn’t improve your control flow • Branching and parallel execution are still problems
Enter Async.js Async.jsprovides common patterns for asyncronouscode control flow https://github.com/caolan/async BONUS: Also provides some common functional programming paradigms
Client or Server -side My examples will mostly be Node.js code, but Async.js can be used in both client and server side code
Serial/Parallel Execution Run functions in series… …or parallel Function 1 Function 2 Function 3 Function 4 Function 1 Function 2 Function 3 Function 4
Serial/Parallel Functions async.parallel([ function(){ ... }, function(){ ... } ], callback); async.series([ function(){ ... }, function(){ ... } ]);
Serial/Parallel Functions async.parallel([ function(){ ... }, function(){ ... } ], callback); async.series([ function(){ ... }, function(){ ... } ]); Single Callback!
Waterfall Execution Async also provides a flow for serial execution, passing results to successive functions args args args Function 1 Function 2 Function 3 Function 4
Waterfall async.waterfall([ function(){ callback(arg1); }, function(arg1) { callback(ar2,ar3) }, function(arg1, arg2){ callback(“done”) } ],function(err, results){ // results now equals “done” });
Times Times() offers a shortcut to iterating over a function multiple times in parallel async.times(5, function(n, next) { createUser(n,function(err, user { next(err, user); }) }, function(err, users){ // ‘users’ now contains 5 users });
Collection Management Functional programming provides some useful tools that are becoming mainstream Specifically, map, reduce, and filter operations are now common in many languages
Map The Map operation calls a given function on each item of a list
Map async.map([‘file1’,‘file2’,‘file3’], funct, callback); async.mapSeries([‘file1’,‘file2’], funct, callback); async.mapLimit([‘file1’,‘file2’,‘file3’], limit, funct, callback);
Reduce The Reduce operation aggregates a list of values into a single result, using a specified aggregation function
Reduce Initial result state async.reduce([val1,val2,…],memo, function(memo,item,cb){ // doStuff }); async.reduceRight([val1,val2],memo, function(memo,item,cb){ // doStuff });
Filter To minimize computational overhead, it’s often helpful to filter data sets to only operate on acceptable values Async.js provides a Filter function to do just this
Filter async.filter([‘file1’,‘file2’,‘file3’], fs.exists, function(results){ // results is now an array // of files that exist });
In Summation Async.js provides: • Powerful asynchronous control flows • Functional programming tools Write clean code, live the dream.
Thanks! Any questions? @aaroncois