How do I make my asynchronous code look readable/beautiful?
-
I'm looking at my nodejs projects code and it seems like the code is becoming very messy because of node's style of hooking functions to the callback. even if I try to split callbacks into separate functions it still looks a bit messy. I was wondering if any one created a module that allows writing code in a more organized/readable way.
-
Answer:
There are many libraries out there that will help you make your asynchronous code more readable. All have different pros and cons, so your choice will depend on the project you have in mind. Also, you might need to consider which syntax makes the most sense to you, among all of these projects. Here are all the libraries that I have experimented with, so far: Async: https://github.com/caolan/async Step: https://github.com/creationix/step Seq: https://github.com/substack/node-seq Q: https://github.com/kriskowal/q Futures: https://github.com/coolaj86/futures Deferred: https://github.com/medikoo/deferred What is the core difference? On one side, Async, Step and Seq use a simple way to write asynchronous code and make it more readable. Q, Futures and Deffered, on the other side, use the promise pattern. And what is the promise pattern? When using the promise pattern, an asynchronous function will return a promise object. A promise object represents an eventual value. This means that this value may already be available or is expected to be available in the future. Also, a promise can succeed or fail. Hum... Can you show some demo code? Let's consider this piece of asynchronous code, which is not very readable: // Declaration asynchronous functions var stepA = function (cb) { setTimeout(cb.bind(null, 1), 500); }, stepB = stepC = function (v, cb) { setTimeout(cb.bind(null, v + 1), 500); }, stepD = function (v1, v2, cb) { setTimeout(cb.bind(null, v1 * v2), 500); }, finalStep = function () { console.log('All steps accomplished'); }; // End declaration stepA(function (valueA) { console.log('End step A'); stepB(valueA, function(valueB) { console.log('End step B'); // As we launch step C and step D in parallel // we setup an increment // to know when the 2 functions have completed var cpt = 0; stepC(valueB, function(valueC) { console.log('End step C'); cpt++; if (cpt === 2) { finalStep(); } }); stepD(valueA, valueB, function (valueD) { console.log('End step D'); cpt++; if (cpt === 2) { finalStep(); } }) }); }); Now let's see how it would be written with the promise pattern (Deferred library): // Deffered mode // Declaration asynchronous functions var stepA = function (cb) { setTimeout(cb.bind(null, 1), 500); }, stepB = stepC = function (v, cb) { setTimeout(cb.bind(null, v + 1), 500); }, stepD = function (v1, v2, cb) { setTimeout(cb.bind(null, v1 * v2), 500); }, finalStep = function () { console.log('All steps accomplished'); }; // End declaration var deferred = require('deferred'), _valueA = null; // Promisify the functions stepA = deferred.promisify(stepA); stepB = deferred.promisify(stepB); stepC = deferred.promisify(stepC); stepD = deferred.promisify(stepD); // Call step1 stepA() .then(function (valueA) { _valueA = valueA; console.log('End step A'); return stepB(valueA) }) .then(function (valueB) { console.log('End step B'); console.log('Call step C and step D'); // Return a promise wich is completed when the 2 promises are completed return deferred(stepC(valueB), stepD(_valueA, valueB)) }) .then(finalStep); Now with async library : // Declaration asynchronous functions // Change steps to specify at async there is no error (first argument) var stepA = function (cb) { setTimeout(cb.bind(null, null, 1), 500); }, stepB = stepC = function (v, cb) { setTimeout(cb.bind(null, null, v + 1), 500); }, stepD = function (v1, v2, cb) { setTimeout(cb.bind(null, null, v1 * v2), 500); }, finalStep = function () { console.log('All steps accomplished'); }; // End declaration var async = require('async'), _valueA = null; async.waterfall([ stepA, function (valueA, next) { console.log('End step A'); // Copy this variable to access on it on the next scope _valueA = valueA; stepB(valueA, next); }, function (valueB, next) { console.log('End step B'); async.parallel([ function (callback) { console.log('Call step C'); stepC(valueB, callback) }, function (callback) { console.log('Call step D'); stepD(_valueA, valueB, callback) } ], next) }, finalStep ]); Now you understand the two different ways to make your asynchronous code more readable. If you need further explanations, feel free to ask in the comments. Thanks to for pointing me to this question and the help on improving my english.
Sébastien Chopin at Quora Visit the source
Other answers
TL;DR: There are other ways to resume routines when they become available than callbacks. can be alternative one. CPS (continuation-passing style) basically makes code hard to read and write, in particular when it needs imperative features of programming language (e.g. continue, break, goto). When you do CPS, you have to reinvent most of control structures like continue and break that should be implemented by programming language implementations (compilers and interpreters). The alternative solution is using (or continuations) to resume followed routines when an idle socket gets available. You can casually use language features like continue and break with coroutines as you used to do. For example, the following CPS code: function getFollowers(screenName, complete, cursor) { var path = '/1/followers/ids.json?screen_name=' + screenName + '&cursor=' + (cursor || '-1'); http.get({ host: 'api.twitter.com', path: path }, function (res) { res.setEncoding('utf8'); var buffer = ''; res.on('data', function (chunk) { buffer += chunk; }) res.on('end', function () { var data = JSON.parse(buffer); if (data['next_cursor']) { getFollowers(screenName, function (d) { complete(data['ids'] + d); }, data['next_cursor_str']); } else { complete(data['ids']); } }); });}getFollowers('lily199iu', function (data) { console.log(data);}); can get simpler with coroutines: from gevent.monkey import patch_all; patch_all()import urllib2import jsondef get_followers(screen_name): url = 'http://api.twitter.com/1/followers/ids.json' \ '?screen_name={0}&cursor={1}' next_cursor = -1 ids = [] while next_cursor: u = urllib2.urlopen(url.format(screen_name, next_cursor)) data = json.load(u) for id_ in data['ids']: yield id_ next_cursor = data['next_cursor']print list(get_followers(screen_name='lily199iu')) If you want to use coroutines in node.js, try node-fibers (https://github.com/laverdet/node-fibers).
Hong Minhee ã» æ´ª æ°æ
I recently wrote a book on this subject (http://leanpub.com/asyncjs), because it's probably the hottest topic in JavaScript today ("Semicolongate" aside). Sébastien's answer to this question is right on the mark: There are several flow control libraries (Async.js, the most popular, is covered in the book) and there's a design pattern called Promises that, though it's been around for decades, has only recently entered the mainstream programming lexicon. I like Promises so much that they get an entire chapter in the book, and I'll be speaking about them at FluentConf in May. Here's the bad news: The JS landscape is rapidly shifting. There is no "standard" control flow library, and there are several rival implementations of Promises. (Blame the jQuery team for ignoring the CommonJS spec and doing their own thing.) If you're using libraries that are callback-based, converting them to Promise-based code requires extra boilerplateâand vice versa. There's no silver bullet. And yet, I love JavaScript. The way that Node.js came out of nowhere in the last three years is an amazing triumph, and it's proven that JavaScript is a first-rate programming language with advantages beyond its monopoly over the browser. So my advice is: Learn what libraries and techniques people are using, and be creative about organizing your code. Above all else, resist the urge to nest callbacks within callbacks within callbacks. If you think carefully, you can always find another way.
Trevor Burnham
I think another option is try node v0.11.x version, but it's still unstable. from v0.11 nodejs start support https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function* , which will make a chance let you away from Callback pitfalls, vs promise or other patterns, Generators are javascript native: http://jlongster.com/A-Study-on-Solving-Callbacks-with-JavaScript-Generators
Weixiao Fan
I would also recommend the RxJS: http://go.microsoft.com/fwlink/?LinkID=182999 library from Microsoft. Erik Meijer is behind this project, which provide LINQ style combinators to work with asynchronous computation. Here are some examples of usage I have put together over the last couple of months: http://tech.loku.com/tag/rxjs/
Roger Castillo
I like node-fiberize. It uses Fibers to keep your asynchronous code even cleaner than the previous examples: https://github.com/lm1/node-fiberize var fiberize = require('fiberize'); fiberize.start(function() { console.log('Hello'); fiberize.sleep(1000); console.log('World'); });
Ahmed Magdy
Check out the async library: https://github.com/caolan/async It provides all kinds of great flow-control methods to tame the callbacks.
Nicolas LaCasse
You can try to use http://maxtaco.github.com/coffee-script/ IcedCoffeeScript which introduced "await" and "defer" primitives to solve this problem.
Boris Staal
We use forward-declared functions, no libraries required: // Declaration asynchronous functions var stepA = function (cb) { setTimeout(cb.bind(null, 1), 500); }, stepB = stepC = function (v, cb) { setTimeout(cb.bind(null, v + 1), 500); }, stepD = function (v1, v2, cb) { setTimeout(cb.bind(null, v1 * v2), 500); }, finalStep = function () { console.log('All steps accomplished'); }; // End declaration stepA(functionA); function functionA(valueA) { console.log('End step A'); stepB(valueA, functionB); function functionB(valueB) { console.log('End step B'); // As we launch step C and step D in parallel // we setup an increment // to know when the 2 functions have completed var cpt = 0; stepC(valueB, functionC); function functionC(valueC) { console.log('End step C'); cpt++; if (cpt === 2) { finalStep(); } }; stepD(valueA, valueB, functionD); function functionD(valueD) { console.log('End step D'); cpt++; if (cpt === 2) { finalStep(); } }; }; };
Darren Hobbs
Related Q & A:
- How do I make my paper look aged?Best solution by Yahoo! Answers
- How can I make my room look amazing when I clean it?Best solution by Yahoo! Answers
- How can I make my profile look cooler?Best solution by Yahoo! Answers
- How can I make a nissan altima coupe look hardcore?Best solution by Yahoo! Answers
- How can I make a music video look more professional?Best solution by makeuseof.com
Just Added Q & A:
- How many active mobile subscribers are there in China?Best solution by Quora
- How to find the right vacation?Best solution by bookit.com
- How To Make Your Own Primer?Best solution by thekrazycouponlady.com
- How do you get the domain & range?Best solution by ChaCha
- How do you open pop up blockers?Best solution by Yahoo! Answers
For every problem there is a solution! Proved by Solucija.
-
Got an issue and looking for advice?
-
Ask Solucija to search every corner of the Web for help.
-
Get workable solutions and helpful tips in a moment.
Just ask Solucija about an issue you face and immediately get a list of ready solutions, answers and tips from other Internet users. We always provide the most suitable and complete answer to your question at the top, along with a few good alternatives below.