Avoiding Callback Hell with Async.js

43
Avoiding Callback Hell with Async.js Aaron Cois, Ph.D. www.codehenge.net

description

This talk was given at JSSummit 2013. Entitled "Avoiding Callback Hell with Async.js", my talk focused on common pitfalls with asynchronous functions and callbacks in JavaScript, and using the async.js library and its advanced control flows to create cleaner, more manageable code.

Transcript of Avoiding Callback Hell with Async.js

Page 1: Avoiding Callback Hell with Async.js

Avoiding Callback Hell with Async.js

C. Aaron Cois, Ph.D. www.codehenge.net

Page 2: Avoiding Callback Hell with Async.js

Sup

@aaroncois

www.codehenge.net

github.com/cacois

Page 3: Avoiding Callback Hell with Async.js

So, JavaScript?

Page 4: Avoiding Callback Hell with Async.js

What’s cool?• Robust event model• Asynchronous programming• Client-side and Server-side

Page 5: Avoiding Callback Hell with Async.js

What’s cool?• Robust event model• Asynchronous programming• Client-side and Server-side

Page 6: Avoiding Callback Hell with Async.js

What can cause headaches?

Page 7: Avoiding Callback Hell with Async.js

Callbacks

JavaScript uses callback functions to handle asynchronous control flow

Page 8: Avoiding Callback Hell with Async.js

Anatomy of a Callback fs = require('fs');  fs.readFile('f1.txt','utf8',function(err,data){     if (err) {        return console.log(err);     }     console.log(data); });

Page 9: Avoiding Callback Hell with Async.js

Anatomy of a Callback fs = require('fs');  fs.readFile('f1.txt','utf8',function(err,data){     if (err) {        return console.log(err);     }     console.log(data); });

Anonymous, inline callback

Page 10: Avoiding Callback Hell with Async.js

Anatomy of a Callback fs = require('fs');  fs.readFile('f1.txt','utf8', function(err,data){     if (err) {        return console.log(err);     }     console.log(data); } );

Equivalentformatting

Page 11: Avoiding Callback Hell with Async.js

Callback Hell

When working with callbacks, nesting can get quite out of hand…

Page 12: Avoiding Callback Hell with Async.js

Callback Hellfunc1(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… }); }); }); }); });      }); });  });});

Page 13: Avoiding Callback Hell with Async.js

Callback Hellfunc1(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… }); }); }); }); });      }); });  });});

Page 14: Avoiding Callback Hell with Async.js

Best case, this is linearfunc1

func2

func3

. . .

func9

Page 15: Avoiding Callback Hell with Async.js

But it can branchfunc1

func2

func3

. . .

func9

func2

func3func3 func3

. . .

func9

. . .

func9

. . .

func9

Page 16: Avoiding Callback Hell with Async.js

But it can branchfunc1

func2

func3

. . .

func9

func2

func3func3 func3

. . .

func9

. . .

func9

. . .

func9

… func2(param, function(err, results) { _.each(results, func3(param, function(err, res) { func4(param, function(err, res) { … }); }   });});

Page 17: Avoiding Callback Hell with Async.js

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

Page 18: Avoiding Callback Hell with Async.js

A more ‘real’ examplevar db = require('somedatabaseprovider');//get recent postshttp.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]);      });    }  });});

Page 19: Avoiding Callback Hell with Async.js

A more ‘real’ examplevar db = require('somedatabaseprovider');//get recent postshttp.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]);      });    }  });});

Page 20: Avoiding Callback Hell with Async.js

Solutions• You can make this easier to read by

separating anonymous functions

• Passing function references instead of anonymous functions helps even more

Page 21: Avoiding Callback Hell with Async.js

Inline callback fs = require('fs');  fs.readFile('f1.txt','utf8',function(err,data){     if (err) {        return console.log(err);     }     console.log(data); });

Page 22: Avoiding Callback Hell with Async.js

Separate Callback fs = require('fs');  callback = function(err,data){  if (err) {    return console.log(err);   }   console.log(data); }

fs.readFile('f1.txt','utf8',callback);

Page 23: Avoiding Callback Hell with Async.js

Can turn this: var db = 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]);      });    }  });});

Page 24: Avoiding Callback Hell with Async.js

…into this var db = require('somedatabaseprovider');  http.get('/recentposts', afterRecentPosts);  function afterRecentPosts(req, res) {   db.openConnection('host', creds, function(err, conn) { afterDBConnected(res, conn); }); } function afterDBConnected(err, conn) {   res.param['posts'].forEach(post) {     conn.query('select * from users where id='+post['user'],afterQuery);   } } function afterQuery(err, results) {   conn.close();   res.send(results[0]); }

Page 25: Avoiding Callback Hell with Async.js

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

Page 26: Avoiding Callback Hell with Async.js

Enter Async.js

Async.js provides common patterns for asyncronous code control flow

https://github.com/caolan/async

BONUS: Also provides some common functional programming paradigms

Page 27: Avoiding Callback Hell with Async.js

Client or Server -sideMy examples will mostly be Node.js code, but Async.js can be used in both client and server side code

Page 28: Avoiding Callback Hell with Async.js

Serial/Parallel ExecutionRun functions in series…

…or parallel

Function 1

Function 2

Function 3

Function 4

Function 1

Function 2

Function 3

Function 4

Page 29: Avoiding Callback Hell with Async.js

Serial/Parallel Functions async.parallel([    function(){ ... },    function(){ ... } ], callback);  async.series([    function(){ ... },    function(){ ... } ]);

Page 30: Avoiding Callback Hell with Async.js

async.parallel([    function(){ ... },    function(){ ... } ], callback);  async.series([    function(){ ... },    function(){ ... } ]);

Serial/Parallel Functions

Single Callback!

Page 31: Avoiding Callback Hell with Async.js

Waterfall ExecutionAsync also provides a flow for serial execution, passing results to successive functions

Function 1

Function 2

Function 3

Function 4

args args args

Page 32: Avoiding Callback Hell with Async.js

Waterfall async.waterfall([   function(){ callback(arg1); },   function(arg1) { callback(ar2,ar3) }, function(arg1, arg2){ callback(“done”) } ], function(err, results){ // results now equals “done” });

Page 33: Avoiding Callback Hell with Async.js

TimesTimes() 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 });

Page 34: Avoiding Callback Hell with Async.js

Let’s see some code…

Page 35: Avoiding Callback Hell with Async.js

Collection ManagementFunctional programming provides some useful tools that are becoming mainstream

Specifically, map, reduce, and filter operations are now common in many languages

Page 36: Avoiding Callback Hell with Async.js

Map

The Map operation calls a given function on each item of a list

Page 37: Avoiding Callback Hell with Async.js

Map async.map([‘file1’,‘file2’,‘file3’], funct, callback);  async.mapSeries([‘file1’,‘file2’], funct, callback);

async.mapLimit([‘file1’,‘file2’,‘file3’], limit, funct, callback);

Page 38: Avoiding Callback Hell with Async.js

Reduce

The Reduce operation aggregates a list of values into a single result, using a specified aggregation function

Page 39: Avoiding Callback Hell with Async.js

Reduce async.reduce([val1,val2,…],memo, function(memo,item,cb){ // doStuff });  async.reduceRight([val1,val2],memo, function(memo,item,cb){ // doStuff });

Initial result state

Page 40: Avoiding Callback Hell with Async.js

FilterTo 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

Page 41: Avoiding Callback Hell with Async.js

Filter async.filter([‘file1’,‘file2’,‘file3’], fs.exists, function(results){ // results is now an array // of files that exist });

Page 42: Avoiding Callback Hell with Async.js

In SummationAsync.js provides:

• Powerful asynchronous control flows• Functional programming tools

Write clean code, live the dream.

Page 43: Avoiding Callback Hell with Async.js

Thanks!

Any questions?

@aaroncois