Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Belgorodjs-async

 Belgorodjs-async

Discussing control flow frameworks.

Avatar for Alexey Maslennikov

Alexey Maslennikov

November 01, 2013
Tweet

Other Decks in Programming

Transcript

  1. Managing Asynchronous JavaScript Writing Maintainable and Robust Node.js Code Alex

    Maslennikov [email protected] #belgorodjs 11.2013 A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 1 / 36
  2. Outline Motivation Asynchronous and Synchronous Code Frustration of Managing Asynchronous

    Code Control Flow Tools Definintion Asynchronous Code Flow Patterns Existing Control Flow Tools Next Generation Tools: Generator-Based Control Flow Summary A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 2 / 36
  3. Motivation Asynchronous and Synchronous Code Outline Motivation Asynchronous and Synchronous

    Code Frustration of Managing Asynchronous Code Control Flow Tools Definintion Asynchronous Code Flow Patterns Existing Control Flow Tools Next Generation Tools: Generator-Based Control Flow Summary A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 3 / 36
  4. Motivation Asynchronous and Synchronous Code Synchronous is Blocking Definition a

    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
  5. Motivation Asynchronous and Synchronous Code Asynchronous is Non-Blocking Definition asynchronous

    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
  6. Motivation Asynchronous and Synchronous Code Pros and Cons of Two

    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
  7. Motivation Frustration of Managing Asynchronous Code Outline Motivation Asynchronous and

    Synchronous Code Frustration of Managing Asynchronous Code Control Flow Tools Definintion Asynchronous Code Flow Patterns Existing Control Flow Tools Next Generation Tools: Generator-Based Control Flow Summary A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 7 / 36
  8. Motivation Frustration of Managing Asynchronous Code Pyramid of Doom Example

    db.query(query, function(error, users) { if (!error) { db.query(query, function(error, posts) { if (!error) { db.query(query, function(error, comments) { if (!error) { console.log(comments); } else { // Handle error } }); } else { // Handle error } }); } else { // Handle error } }); A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 8 / 36
  9. Motivation Frustration of Managing Asynchronous Code Pitfalls of Asynchronous Code

    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
  10. Motivation Frustration of Managing Asynchronous Code Pitfalls of Asynchronous Code

    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
  11. Motivation Frustration of Managing Asynchronous Code Pitfalls of Asynchronous Code

    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
  12. Motivation Frustration of Managing Asynchronous Code Pitfalls of Asynchronous Code

    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
  13. Motivation Frustration of Managing Asynchronous Code Pitfalls of Asynchronous Code

    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
  14. Motivation Frustration of Managing Asynchronous Code Pitfalls of Asynchronous Code

    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
  15. Motivation Frustration of Managing Asynchronous Code What Would We Expect

    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
  16. Control Flow Tools Definintion Outline Motivation Asynchronous and Synchronous Code

    Frustration of Managing Asynchronous Code Control Flow Tools Definintion Asynchronous Code Flow Patterns Existing Control Flow Tools Next Generation Tools: Generator-Based Control Flow Summary A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 12 / 36
  17. Control Flow Tools Definintion Definition of a Control Flow Tool

    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
  18. Control Flow Tools Asynchronous Code Flow Patterns Outline Motivation Asynchronous

    and Synchronous Code Frustration of Managing Asynchronous Code Control Flow Tools Definintion Asynchronous Code Flow Patterns Existing Control Flow Tools Next Generation Tools: Generator-Based Control Flow Summary A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 14 / 36
  19. Control Flow Tools Asynchronous Code Flow Patterns Full Synchronous Code

    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
  20. Control Flow Tools Asynchronous Code Flow Patterns Full Parallel Code

    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
  21. Control Flow Tools Asynchronous Code Flow Patterns Mixed Code Flow

    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
  22. Control Flow Tools Existing Control Flow Tools Outline Motivation Asynchronous

    and Synchronous Code Frustration of Managing Asynchronous Code Control Flow Tools Definintion Asynchronous Code Flow Patterns Existing Control Flow Tools Next Generation Tools: Generator-Based Control Flow Summary A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 18 / 36
  23. Control Flow Tools Existing Control Flow Tools /caolan/async Provides around

    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
  24. Control Flow Tools Existing Control Flow Tools /caolan/async Example async.map([’file1’,’file2’,’file3’],

    fs.stat, function(err, results){ // results is now an array of stats for each file }); async.filter([’file1’,’file2’,’file3’], fs.exists, function(results){ // results now equals an array of the existing files }); async.parallel([ function(){ ... }, function(){ ... } ], callback); async.series([ function(){ ... }, function(){ ... } ]); A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 20 / 36
  25. Control Flow Tools Existing Control Flow Tools /kriskowal/q A very

    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
  26. Control Flow Tools Existing Control Flow Tools /kriskowal/q Example (Adapting

    the Interface) function readFile(filename) { var deferred = Q.defer(); FS.readFile(filename, ’utf-8’, function (error, text) { if (error) { deferred.reject(new Error(error)); } else { deferred.resolve(text); } }); //immediately returning a promise return deferred.promise; } A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 22 / 36
  27. Control Flow Tools Existing Control Flow Tools /kriskowal/q Example (Chaining

    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
  28. Control Flow Tools Existing Control Flow Tools /kriskowal/q Example (Chaining

    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
  29. Control Flow Tools Existing Control Flow Tools /kriskowal/q More real-life

    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
  30. Control Flow Tools Existing Control Flow Tools /geeqie/flowy Flowy aspires

    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
  31. Control Flow Tools Existing Control Flow Tools /geeqie/flowy In its

    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
  32. Control Flow Tools Existing Control Flow Tools /geeqie/flowy Flowy goes

    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
  33. Control Flow Tools Next Generation Tools: Generator-Based Control Flow Outline

    Motivation Asynchronous and Synchronous Code Frustration of Managing Asynchronous Code Control Flow Tools Definintion Asynchronous Code Flow Patterns Existing Control Flow Tools Next Generation Tools: Generator-Based Control Flow Summary A. Maslennikov Managing Asynchronous JavaScript #belgorodjs 11.2013 28 / 36
  34. Control Flow Tools Next Generation Tools: Generator-Based Control Flow Generator

    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
  35. Control Flow Tools Next Generation Tools: Generator-Based Control Flow Generator

    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
  36. Control Flow Tools Next Generation Tools: Generator-Based Control Flow Generator

    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
  37. Control Flow Tools Next Generation Tools: Generator-Based Control Flow Generator

    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
  38. Control Flow Tools Next Generation Tools: Generator-Based Control Flow Generator

    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
  39. Control Flow Tools Next Generation Tools: Generator-Based Control Flow Generator

    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
  40. Control Flow Tools Next Generation Tools: Generator-Based Control Flow Generator

    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
  41. Control Flow Tools Next Generation Tools: Generator-Based Control Flow Generator

    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
  42. Control Flow Tools Next Generation Tools: Generator-Based Control Flow How

    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
  43. Control Flow Tools Next Generation Tools: Generator-Based Control Flow /laverdet/node-fibers

    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
  44. Control Flow Tools Next Generation Tools: Generator-Based Control Flow /visionmedia/co

    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
  45. Control Flow Tools Next Generation Tools: Generator-Based Control Flow /visionmedia/co

    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
  46. Summary Summary Writing asynchronous code without any helping tools can

    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
  47. Appendix For Further Reading Mentioned Projects https://github.com/caolan/async - an async

    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