Control Flow

Raynos, github

notes: off

Control Flow

Control Flow are techniques for managing asynchronous code.

Why you want flow control

A simple task - Showing a blog post

  • Open a database connection
  • Get the blog collection
  • Get the blog post
  • get the author collection
  • get the author data
  • Get the comments collection
  • Get the user collection
  • Iterate over comments
  • Get each comment from the database
  • Get the user data for each comment

Pyramid Of Doom

			step1(function (value1) {
			    step2(function (value2) {
			        step3(function (value3) {
			            step4(function (value4) {
			                step5(function (value5) {
			                    step6(function (value6) {
			                        // Do something with value6
			                    });
			                });
			            });
			        });
			    });
			});
			

Control Flow primitives

  • Named functions
  • Reference counting
  • next functions
  • event emitters

Named functions

			articlesCollection.find({}, { 'sort':[['title', 1]] }, iterateCursors);
			 
			function iterateCursors(err, cursor) {
                cursor.each(logArticle);
            });
             
            function logArticle(err, article) {
	            if(article != null) {
	                console.log("[" + article.title + "]");
	                console.log(">> Closing connection");
	                db.close();
	            }
	        }
			

Oops closures

Storing data in closure scope causes issues with indenting. This can not be solved by moving them into seperate named functions

  • Bind
  • pass state in
  • Put similar functions on an object
  • pd.bindAll

Reference Counting

			var count = post.comments.length,
			    comments = [];
			post.comments.forEach(fetchComment);
			 
			function fetchComment(commentId) {
			    comments.find({ id: commentId }, addToComments);
			}
			 
			function addToComments(err, result) {
			    if (err) throw err;
			    comments.push(result);
			    if (--count === 0) next();
			}
			 
			function next() {
			    /* do stuff with comments */
			}
			

Next functions

			(function loop() {
			    var task = stack.shift();
			    task && task(someData, proxy);
			 
			    function proxy(result) {
			        /* do something with result */
			        loop();
			     }
			}());
			
Technique used by TJ in connect

Event emitters

			var req = http.request(options);
			req.on("response", function (res) {
			    res.on("data", function (chunk) {
			        console.log("BODY: " + chunk);
			    })
			});
			req.on("error", function (error) {
			    console.log("oops: ", error);
			});
			req.end();
			

control Flow abstractions

After

				var next = after(arr.count, finished);
				arr.forEach(doSomethingAsync);
				 
				function doSomethingAsync(item) {
				    somethingAsync(item, next);
				}
				 
				function finished() {
				    var results = arguments;
				    /* do some stuff with your results */
				}
			

After uses reference counting internally and hides the details from you

Modifying the count

			    var files = [],
			        next = after(1, finished);
			    fs.readdir(somePath, readFolder);
			     
				function readFolder(err, files) {
				    fs.readdir(loc, function (err, files) {
				        next.count += files.length;
				        files.forEach(function (file) {
				            var filePath = path.join(loc, file);
				            isFile(file) ? fs.readFile(filePath, readFile) : fs.readdir(filePath, readFolder);
				        });
				        next.count--;
				    });
				}
				 
				function readFile(err, file) {
				    files.push(file);
				    next();
				}
			

Set Iterations

				after.map(post.comments, mapToComment, finished);
				 
				function mapToComment(value, callback) {
				    comments.find({ id: value }, callback);
				}
				 
				function finished(err, comments) {
				    /* do stuff with comments */
				}
			

Set iterations allow you to do something with a value in parallel. This is a great sugar tool

Stacks

				var fs = require("fs"),
				    exec = require("child_process").exec,
				    after = require("after"),
				    stack = Stak.make(function () {
				        exec('whoami', this.next);
				    }, function () {
				        var next = after(2, this.next);
				        exec('groups', function (err, groups) {
				            next("groups", groups);
				        });
				        fs.readFile(this.file, 'ascii', function (err, file) {
				            next("file", file);
				        });
				    }, function () {
				        var data = after.unpack(arguments);
				        console.log("Groups : ", data.groups.trim());
				        console.log("This file has " data.file.length + " bytes");
				    });
				 
				stack.handle({ file: __filename });
			

Open source alternatives

Questions

Question time, Audience Go!

Rewrote the mongodb blog example using stak