synchronous function call blocks the main program flow until it returns Example (Synchronous Call) var val = getValue(); // proceeding only after ‘getValue()‘ call returns // all this time node.js thread is blocked A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 4 / 36
events are those occurring independently of the main program flow asynchronous function calls are executed in a non-blocking manner, allowing the main program flow to continue processing Example (Asynchronous Call) fs.readFile(__filename, ’utf8’, function(err, data) { console.log(err || data); }); // proceed immediately, file is read independent of theprogram flow // node.js event loop won’t be blocked A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 5 / 36
Approaches Synchronous + familiar approach for every developer + obvious and predictable execution flow – launching concurrent tasks needs additional effort Asynchronous + launching of parallel and consecutive tasks is naturally expressed – can be uncomfortable for a non-accustomed developer – implementing relatively complex logic becomes a nontrivial task A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 6 / 36
Task Read a thousand text files and pass an aggregated result to the processing routine as array of strings. Example (Naive Implementation) var result = []; filenames.forEach(function(filename) { fs.readFile(filename, function(err, data) { data && result.push(data); }); }); processData(result); A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 9 / 36
Task Read a thousand text files and pass an aggregated result to the processing routine as array of strings. Example (Naive Implementation) var result = []; filenames.forEach(function(filename) { fs.readFile(filename, function(err, data) { data && result.push(data); }); }); processData(result); Starting 1000 simultaneous asynchronous file reads, and running the processData() immediately. A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 9 / 36
Task Read a thousand text files and pass an aggregated result to the processing routine as array of strings. Requirements A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 10 / 36
Task Read a thousand text files and pass an aggregated result to the processing routine as array of strings. Requirements we need to wait until all files are read completely before calling processData(); A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 10 / 36
Task Read a thousand text files and pass an aggregated result to the processing routine as array of strings. Requirements we need to wait until all files are read completely before calling processData(); launching a 1000 file reads can exhaust the number of available file handles — we need to read files in small batches; A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 10 / 36
Task Read a thousand text files and pass an aggregated result to the processing routine as array of strings. Requirements we need to wait until all files are read completely before calling processData(); launching a 1000 file reads can exhaust the number of available file handles — we need to read files in small batches; we need to collect file contents in exactly the same order their names appear in initial filenames array. A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 10 / 36
From a Tool A way to control the order in which the file reads are done Some way to collect the result data for processing Some way to restrict the concurrency of the file read operations to conserve limited system resources A way to determine when all the reads necessary for the processData() are completed A uniform way to handle error situations A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 11 / 36
A control flow tool is a lightweight, generic piece of code which runs in between several asynchronous function calls taking care of the necessary housekeeping to: control the order of execution, collect data, limit concurrency, handle errors and call the next step in the program. A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 13 / 36
Flow Sometimes we just want to do one thing at a time. For example, we need to do five database queries, and each of those queries needs data from the previous query, so we have to run one after another. Characteristics Runs a number of operations sequentially Only starts one async operation at a time (no concurrency) Ensures async functions to complete in the defined order A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 15 / 36
Flow We just want to take a set of operations, launch them all in parallel and then do something with results of their execution when all of them are complete. Characteristics Starts all async operations in parallel (full concurrency) No guarantee of order, only that all the operations have been completed Collects an aggregated result A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 16 / 36
The most often occuring in real life pattern. Curiously, the worst maintained by the popular control-flow tools. Characteristics Runs a limited number of operations in parallel or Is a composition of previous two patterns Collects an aggregated result A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 17 / 36
20 functions that include the usual ’functional’ suspects (map, reduce, filter, each, . . . ) as well as some common patterns for asynchronous control flow (parallel, series). Opinion + preserves the purity of classical node.js interfaces – looks like a quick solution for some dirty scripting A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 19 / 36
popular implementation of promises concept [1]. Almost 700 npm packages depend on this library. Opinion + scalable solution + flexible error handling – OOP-style of controlling asynchronous flow looks a little bit bulky – code becomes heavily dependend on the promises interface: functions receive no more callbacks but return a promise object to allow chaining. A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 21 / 36
Async Steps) Q.fcall(step1) .then(step2) .then(step3) .then(function (value3) { // Do something with value3 }, function (error) { // Handle any error from step1 through step3 }) .done(); // <-- without it unhandled exceptions would be lost! A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 23 / 36
Async Steps) Q.fcall(step1) .then(step2) .then(step3) .then(function (value3) { // Do something with value3 }, function (error) { // Handle any error from step1 through step3 }) .done(); // <-- without it unhandled exceptions would be lost! All these .then() become very noizy at some point of time Forgetting to terminate the chain can be the reason of some debugging fun A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 23 / 36
example nevertheless starts looking cumbersome. For example, when we are trying to execute some chain elements concurrently or receiving/returning more than one value from one step. Example (Passing Multiple Values to the Next Step) return getUser(username) .then(function (user) { //some magic here returning both value and promise return [user, getUserData(user.id)]; }) .spread(function(user, data) { //working with user and his data }); A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 24 / 36
to be more elegant and flexible solution as the q library. It is an ancestor of the creationx/step tool. Opinion + Allows to leave application interfaces pure node.js-async-style + Very natural representation of parallel and consecutive execution + Sets no limits to the number of receiving and returning parameters for its steps + Can be used with promise-based APIs + Flexible error handling + You have already understood that it is my library and here will be only "+" :) A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 25 / 36
core, Flowy uses very similar to kriskowal/q promises mechanism: Example (Exposing the Flowy promises API) function leaveMessage(username, text, callback) { Flowy.chain(function() { model.users.findOne(username, this.slot()); }).then(function(err, user) { if (!user) throw new Error(’user not found’); model.messages.create(user, text, this.slot()); }).then(function(err, message) { model.notifications.create(message, this.slot()); }).end(callback); //any error will be propagated to this point But the code above inherits some of the q’s disadvantages. A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 26 / 36
one step further: Example (Flowy Hiding its Internals) function getUserMessages(username, callback) { Flowy( function() { // parallel execution model.users.findOne(username, this.slot()); model.messages.find(username, this.slot()); }, function(err, user, messages) { if (!user) throw new Error(’user not found’); // making something with messages... and eventually: this.pass(messages); }, callback //errors will be propagated to the callback properly } A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 27 / 36
Basics Let’s look at a raw generator function before we dive into async land. We’ll define a generator with function* and then establish communication between it and its owner through the ’yield/next’ pair: Example (Simple Generator Function) function* foo(x) { var a = yield x + 1; var b = yield a * 2; return a + b + x; } A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 29 / 36
Basics Let’s look at a raw generator function before we dive into async land. We’ll define a generator with function* and then establish communication between it and its owner through the ’yield/next’ pair: Example (Simple Generator Function) function* foo(x) { var a = yield x + 1; var b = yield a * 2; return a + b + x; } var res; var gen = foo(); A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 29 / 36
Basics Let’s look at a raw generator function before we dive into async land. We’ll define a generator with function* and then establish communication between it and its owner through the ’yield/next’ pair: Example (Simple Generator Function) function* foo(x) { // x = 3 var a = yield x + 1; var b = yield a * 2; return a + b + x; } var res; var gen = foo(); res = gen.next(3); A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 29 / 36
Basics Let’s look at a raw generator function before we dive into async land. We’ll define a generator with function* and then establish communication between it and its owner through the ’yield/next’ pair: Example (Simple Generator Function) function* foo(x) { var a = yield x + 1; var b = yield a * 2; return a + b + x; } var res; var gen = foo(); res = gen.next(3); // res = {value: 4, done: false} A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 29 / 36
Basics Let’s look at a raw generator function before we dive into async land. We’ll define a generator with function* and then establish communication between it and its owner through the ’yield/next’ pair: Example (Simple Generator Function) function* foo(x) { var a = yield x + 1; // a = 10 var b = yield a * 2; return a + b + x; } var res; var gen = foo(); res = gen.next(3); // res = {value: 4, done: false} res = gen.next(10); A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 29 / 36
Basics Let’s look at a raw generator function before we dive into async land. We’ll define a generator with function* and then establish communication between it and its owner through the ’yield/next’ pair: Example (Simple Generator Function) function* foo(x) { var a = yield x + 1; var b = yield a * 2; return a + b + x; } var res; var gen = foo(); res = gen.next(3); // res = {value: 4, done: false} res = gen.next(10); // res = {value: 20, done: false} A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 29 / 36
Basics Let’s look at a raw generator function before we dive into async land. We’ll define a generator with function* and then establish communication between it and its owner through the ’yield/next’ pair: Example (Simple Generator Function) function* foo(x) { var a = yield x + 1; var b = yield a * 2; // b = 100 return a + b + x; } var res; var gen = foo(); res = gen.next(3); // res = {value: 4, done: false} res = gen.next(10); // res = {value: 20, done: false} res = gen.next(100); A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 29 / 36
Basics Let’s look at a raw generator function before we dive into async land. We’ll define a generator with function* and then establish communication between it and its owner through the ’yield/next’ pair: Example (Simple Generator Function) function* foo(x) { var a = yield x + 1; var b = yield a * 2; return a + b + x; } var res; var gen = foo(); res = gen.next(3); // res = {value: 4, done: false} res = gen.next(10); // res = {value: 20, done: false} res = gen.next(100); // res = {value: 113, done: true} A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 29 / 36
to Use Generators Generator yields the result of call to async function Owner calls next() only after the callback of this async call was triggered Example (Using Generator To Control Async Flow) suspendable(function*(resume) { var data = yield fs.readFile(__filename, ’utf8’, resume); console.log(data); }); // primitive implementation of suspendable function suspendable(generatorConstructor) { // binding this callback as a ’resume’ argument var generator = generatorConstructor(function(err, value) { generator.next(value); }); generator.next(); } A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 30 / 36
Implementing generators as a C/C++ node.js extension Has a number of nice-looking control-flow frameworks based on it (see /0ctave/node-sync) Opinion + Scary to use, but the solution seems to work somehow – Rather complex implementation needs some time to understand – Nearly impossible to patch it or to understand something inside the library A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 31 / 36
With the introduction in recent v8 versions of its own generator implementation, the number of generator-based control-flow tools starts growing rapidly. They offer very smooth control flow functionality keeping very thin layer between native generators and the application. "The ultimate generator based flow-control goodness for nodejs (supports thunks, promises, etc)" — visionmedia/co A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 32 / 36
Example // consecutive execution co(function *(){ var a = yield get(’http://google.com’); var b = yield get(’http://yahoo.com’); var c = yield get(’http://cloudup.com’); console.log(a.status); console.log(b.status); console.log(c.status); })() // concurrent execution co(function *(){ var a = get(’http://google.com’); var b = get(’http://yahoo.com’); var c = get(’http://cloudup.com’); var res = yield [a, b, c]; console.log(res); })() A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 33 / 36
become an unmanageable task There is a (huge) number of tools providing a convenient facility for writing asynchronous logic for node.js The community is revealing a growing tendency of using a generator-based approach. A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 34 / 36
library providing some functional and flow utilities to manage asynchronous calls written by Caolan McMahon https://github.com/kriskowal/q - a very popular promises concept implementation written by Kris Kowal https://github.com/creationx/step - an early-days prototype of control flow tool written by Tim Caswell https://github.com/geeqie/flowy - a successor of step with the promises-based core written by Alex Maslennikov https://github.com/laverdet/node-fibers - a C++ node.js extension providing generators functionality witten by Marcel Laverdet https://github.com/0ctave/node-sync - a control flow tool based on the node-fibers project written by Yuriy Bogdanov https://github.com/visionmedia/co - a harmony-generators-based control flow tool written by TJ Holowaychuk A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 35 / 36