TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

Node.js - A giant step backwards?

105 pointsby jemeshsuover 13 years ago

26 comments

danhover 13 years ago
Allow me to be a little grumpy:<p>I hate tabloidy headlines with question marks. As in "Queen Elizabeth: Is She a Transvestite?", "The Moon: Is It Made of Cheese?" or "Linkbait: Will It Ever End?".
评论 #3511448 未加载
评论 #3511393 未加载
评论 #3511363 未加载
peterhuntover 13 years ago
The big reason callback-based systems are hard (despite the fact that built-in flow control constructs need to be re-implemented) is that functions are no longer composable. That is, if I write a bunch of code that doesn't need to do any I/O, I'll just return a value to the caller. If at any point in the future the spec changes and <i>any</i> function in this call stack needs to do any I/O, all the code that ends up calling this function needs to be refactored to use callbacks.<p>There really should be language support for this sort of thing (like coroutines) so these sort of cascading changes don't need to happen.
评论 #3511193 未加载
评论 #3511241 未加载
评论 #3512185 未加载
评论 #3511234 未加载
评论 #3511198 未加载
评论 #3514476 未加载
hasenjover 13 years ago
In my experience, node.js is a terrible choice for most web applications. If you really need real-time, sure, go for it. But if you're interested in node as an alternative to rails/sinatra/django/flask, then stay away from it. The cool stuff you get for free like coffeescript/jade/stylus can also work with the ruby and python frameworks.<p>Node sucks for general web apps because you have to program everything asynchronously. This is a major step backwards, and quite frankly it feels to me like trying to program in assembly. It's not expressive at all. You have to write your program in some pseudo code, then translate that to async code. And what for? What's the advantage you'll get? scalability? Who says you will need it? This is exactly where you should remember that premature optimization is the root of all evil.
评论 #3511387 未加载
评论 #3511333 未加载
mixuover 13 years ago
This is really old - discussion from 6 months ago: <a href="http://news.ycombinator.com/item?id=2848239" rel="nofollow">http://news.ycombinator.com/item?id=2848239</a><p>Basically, async I/O gives you more options than "block the whole world while you go read this stuff", and that means that old idioms aren't effective.<p>You gain more control: can choose when to block, when to limit concurrency and when to just launch a bunch of tasks at the same time. At the same time, you need to adopt a few new patterns, since you can't/don't feel right blocking execution every time you use an external data source. It's definitely a tradeoff and not a magic bullet.<p>For my longer take on this, see <a href="http://book.mixu.net/ch7.html" rel="nofollow">http://book.mixu.net/ch7.html</a>
评论 #3511202 未加载
评论 #3512942 未加载
评论 #3511064 未加载
klsover 13 years ago
While I agree that event programming is very different, some of the issues the author brings up can be dealt with by architecting a program for an event based system. I understand that, is the authors gripe. That it is sometimes hard for someone coming from a non event control flow background to adapt at first. But items like the loop example are examples of mixing half control flow and half event based programming. What should be done in that situation is that it should not be returning a list, it should be returning a promise that will get called on completion of the list. Or a more elegant solution would be to notify listeners when a new item of the list is parsed so that they can observe it and see if it is an item that they are interested in. I understand the authors frustration, but it appears to me, that some more articles on best practices would help bring clarity on how to deal with these form of patterns.
vvcepheiover 13 years ago
I hope this isn't too pedantic, but I wish the author would stay away from calling Node "concurrent". The whole point of Node is that it's asynchronous but not concurrent: there is a single thread of execution for your whole program, which is what lets you ignore locking, etc.<p>In fact, when you program for Node it's really important to keep this in mind, since (contrary to another statement from the article) not all libraries are asynchronous. If you select a synchronous db driver or write a long-running loop, it will block the rest of your program.<p>In general, though, I thought it was a good piece. I'm sure many heads have exploded on first introduction to node (and JS in general).
评论 #3511218 未加载
评论 #3511602 未加载
评论 #3511559 未加载
评论 #3511205 未加载
EGregover 13 years ago
This guy doesn't know much about javascript, I am guessing. I made some gists that take his code and add minimal changes to it, that fix the problems he complains about:<p>"Two different code paths, can't do DRY" really? <a href="https://gist.github.com/1678395" rel="nofollow">https://gist.github.com/1678395</a><p>"Oh noo, I can't return the results because they are async". That's what callbacks are for. You know what you CAN do? Do I/O in parallel that's what! Node makes it easy. <a href="https://gist.github.com/1678415" rel="nofollow">https://gist.github.com/1678415</a><p>Anyway I hope this illustrates the point. The guy says it exactly right in one place: "Once you get your head around thinking in async terms, node.js starts to actually make a lot of sense." And therefore it is not a giant step backwards.<p>There are more elegant ways to write this (see <a href="http://qbix.com/plugins/Q/js/Q.js" rel="nofollow">http://qbix.com/plugins/Q/js/Q.js</a>) but these are just minimal changes to his own code.
CoffeeDregsover 13 years ago
I tend to agree with the post, but I find the one-language-to-rule-them-all thing too compelling to fret too much over asynchronicity. Although it'd probably lead to lots of synchronous code, it would be nice for it to to be easier in nodejs to be synchronous sometimes and asynchronous sometims.<p>I would refactor the code to something like (still not as simple as synchronous):<p><pre><code> asynchronousCache.get("id:3244", function(err, myThing) { var useResult = function(err,_myThing){ // We now have a thing from DB, do something with result // ... } if (myThing) useResult(null, myThing); else asynchronousDB.query("SELECT * from something WHERE id = 3244",useResult);</code></pre>
jrockwayover 13 years ago
The problem is that the author simply didn't notice the refactoring and abstraction opportunities available. (One question to ask yourself: "How do I test this?" If you can't answer that question, the code is wrong.)<p>We'll start with the synchronous example:<p><pre><code> myThing = synchronousCache.get("id:3244"); if (myThing == null) { myThing = synchronousDB.query("SELECT * from something WHERE id = 3244"); } </code></pre> This is verbose and tedious. We should really make the API look like:<p><pre><code> myThing = database.lookup({'id':3244}, {'cache':cache_object}); </code></pre> Let's apply this idea to his asynchronous example. We want the code to look like:<p><pre><code> database.lookup({'id':3244}, {'cache':cache_object}, function(myThing) { // whatever }); </code></pre> So instead of writing this:<p><pre><code> asynchronousCache.get("id:3244", function(err, myThing) { if (myThing == null) { asynchronousDB.query("SELECT * from something WHERE id = 3244", function(err, myThing) { // We now have a thing from DB, do something with result // ... }); } else { // We have a thing from cache, do something with result // ... } }); </code></pre> We need to refactor this. Remember, node.js is a continuation-passing-style language. So let's set a convention and say that every function takes two continuations (success and error).<p>Then, to compose two functions of one argument:<p><pre><code> function f(x, result, error) function g(x, result, error) </code></pre> To:<p><pre><code> h = f o g </code></pre> You write:<p><pre><code> function compose(f, g){ return function(x, result, error){ g(x, function(x_){ f(x_, result, error) }, error); } } </code></pre> (Data flows right-to-left over composition, so "do x, then do y" is written: "do y" o "do x".)<p>Now we can cleanly write a complex program from simple parts. We'll start by creating a result type:<p><pre><code> result = { 'id': null, 'value': null, 'not_found': null } </code></pre> Then, we'll implement cache functions that take keys (as results of this type) and return values (as results of this type). Looking up an entry in cache looks like:<p><pre><code> cache.lookup = function(key, result, error){ new_key = key.copy(); cache.raw_cache.lookup(key.id, function(value){ new_key.result = value; new_key.not_found = false; result(new_key) }, function(error_type, error_msg){ if(error_type == ENOENT){ new_key.not_found = true; result(new_key) } else { error(error_type, error_msg); } }); }; </code></pre> Looking up an entry in the database looks about the same. The key feature is that the "return value" and the "input" are of the same type. That makes composing, in the case of "try various abstract storage layer lookups in a fixed order", very easy. (Yes, the example is contrived.)<p><pre><code> dbapi.lookup = function(key, result, error){ ... }; </code></pre> Now we can very easily implement the logic, "look up a value in the cache, if it's not there, look it up in the database":<p><pre><code> cached_lookup = compose(dbapi.lookup, cache.lookup); cached_lookup(1234, do_next_step, handle_error); </code></pre> You can, of course, generalize compose to something like:<p><pre><code> my_program = do([cache.lookup, dbapi.lookup, print_result]); </code></pre> Writing clean and maintainable code in node.js is the same as writing it in any other language. You need to design your program correctly, and rewrite the parts that aren't designed correctly when you realize that your code is becoming messy.<p>Continuation-passing style is pretty weird, but you do get some benefits over the alternatives. Writing a program with coroutines involves deferring to the scheduler coroutine every so often, littering your code with meaningless lines like "yield();". Using "real" threads is even worse; your code looks like single-threaded code, but different parts of your program are running concurrently. (Did you share any non-thread-safe data structures, like Java's date formatter? Hope not, because you won't know you did until the production code dies at 3am.) Continuation-passing style lets you "pretend" that you are executing multiple threads concurrently, but the structure of the code ensures that only one codepath is running at a time. This means that libraries that don't do IO don't have to be thread safe, since only one "thread" runs at a time.<p>All concurrency models involve trade-offs over other concurrency models. But when comparing them, make sure you're comparing the actual trade-offs, not your programming ability with each model.
评论 #3512165 未加载
评论 #3512229 未加载
评论 #3512198 未加载
评论 #3512635 未加载
prodigal_erikover 13 years ago
Transforming functional or imperative code into continuation-passing style isn't that big a deal. If javascript weren't such a pain in the ass just to parse, there would probably be tools to do that. Maybe coffeescript will do it, but this is why macro-extensible languages are a big win—I'd have the right hooks to easily do it myself rather than waiting for the implementors to officially update the language (or get elbow deep in their internals and hope they accept a huge patch).
评论 #3511491 未加载
shangaslammiover 13 years ago
To make things easier, you can use node-fibers (<a href="https://github.com/laverdet/node-fibers" rel="nofollow">https://github.com/laverdet/node-fibers</a>) to structure your asynchronous code with coroutines or, if you are feeling less adventurous, async (<a href="https://github.com/caolan/async" rel="nofollow">https://github.com/caolan/async</a>) is an excellent helper library for common asynchronous code patterns and it works on the client-side as well.
sekover 13 years ago
This callback programming is the reason i quit node.js. It is easy to get something up and running, but then it feels like i never get out of the chaos i created.<p>This whole thing additional to the whole mess JavaScript is? I never liked it to begin with, but there is no real alternative until Dart is ready. Every time something comes out for JavaScript it adds another abstraction and chaos in my opinion. jQery for example, really impressive to begin with, but when you see what a horrible mess you can create with it...<p>There is a reason why big companies never adopt these things, i can't imagine how it would be to take over a node.js app from someone else.<p>Now you can argue that this takes practice. Crockford may write JS from heaven, but i don't want to invest my time in this language. These inconsistencies are not fun to deal with and when Dart is here, companies will drop it very fast.<p>I am now stuck with Scala, it is the complete opposite. It is complicated to get in, but when you get it, you have a gigantic toolbox to solve every problem the way you want. For web programming i recommend Lift, but when you want to get in fast and a fan of async try Play2.0. Node.js made async popular, it should get credit for that.
评论 #3511279 未加载
MatthewPhillipsover 13 years ago
Use recursion. Fixed:<p><pre><code> asynchronousCache.get("id:3244", function doThing(err, myThing) { if (myThing === null) { asynchronousDB.query("SELECT * from something WHERE id = 3244", function(err, myThing) { // We now have a thing from DB, do something with result doThing(err, myThing); }); return; } else if(err !== null) { // Handle error. return; } // We have a thing. });</code></pre>
评论 #3511109 未加载
ok_craigover 13 years ago
Tame JS (tamejs.org) makes asynchronous code in node.js very very easy.
评论 #3511625 未加载
nagnatronover 13 years ago
I thought that this was going to be a node.js bashing.<p>What a let down.
评论 #3511289 未加载
danbmil99over 13 years ago
The problem is, Javascript continuation syntax is ugly and verbose. All the nested indentation fails to map to our human sensibilities about what the code is actually designed to do.<p>Someone needs to fix this
wicknicksover 13 years ago
I am not convinced with the blog post example. If ordering and null entries really matter, then you must put appropriate instructions to handle them. Ordering can be tackled by adding the blogPostId to each entry, and sorting the resulting collection with this key (assuming that you don't pick up a million+ posts).<p>I advocate polyglot programming, and using node.js for tasks other than what its designed for (server side async programming) might result in unfavorable results.<p>iPods are not lousy because one can't text with them.
gexlaover 13 years ago
My take on this post is that the author was calling Node a giant leap backwards because he believed all code should have the look and feel of Python.<p>But now, he's not so sure.<p>Did I miss anything?<p>ETA: I understand that coding for Node looks and feels weird, but so does coding for Lisp, Smalltalk, Haskell and a long list of other programming languages.
评论 #3512271 未加载
pimeysover 13 years ago
I kinda like the em-synchrony for Ruby. It handles the callbacks with fibers, so my actual code doesn't have the callback hell of Javascript. Although the implementation of Ruby fibers is not-so-nice at this point. I hope they'll fix it in the next versions.
评论 #3511248 未加载
评论 #3511249 未加载
ww520over 13 years ago
Aync codes like threaded codes are different from the simple sync code. These different style coding are there to take advantage of the concurrent benefit of the system. Developers with simple single flow control code background often complain about the extra flow control complexity when it's outside of their comfort zone. Think of it as a level up on your skill.<p>There are libraries out there that add syntactic sugar to make async code look like sync code. Like,<p><pre><code> group ( asyncfunc1() asyncfunc2() asyncfunc3() )</code></pre>
评论 #3511953 未加载
rpledgeover 13 years ago
It takes some work to get used to the change in flow control, but it seems to be worthwhile (at least it has been so far for my project). Coming from a real time/embedded background seems to have helped me because typical those systems are heavily event based. I don't know if node.js will change the world but I think it's worth at least playing with just to get some experience with the programming model.
ricardobeatover 13 years ago
<p><pre><code> getPosts (ids, cb) -&#62; res = [] stash = (err, post) -&#62; res.push post cb(res) if res.length is ids.length db.getPostById(id, stash) for id in ids getPosts [...], (posts) -&#62; # go on... </code></pre> use `res[i] = post` if there is some implicit ordering.
nwjsmithover 13 years ago
Declaring node a 'giant' step backwards is a stretch. Callback spaghetti isn't the problem it set out to solve. It is meant to provide easy(-er?) concurrency. If you measure it against its goals, I think it's pretty good.
scribyover 13 years ago
I wrote a module based on fibers to help with these sort of problems. Take a look at <a href="https://github.com/scriby/asyncblock" rel="nofollow">https://github.com/scriby/asyncblock</a>
brendoncrawfordover 13 years ago
This is a rather sensational article. The author's first example is pretty easily solved:<p><pre><code> getFromCache = function (id, callback) { asynchronousCache.get(['id', id].join(':'), function(err, myThing) { if (myThing == null) { asynchronousDB.query("SELECT * from something WHERE id = $id", {id:id}, function(err, myThing) { callback(myThing); }); } else { callback(myThing); } }); }; getFromCache(3222, function (myThing) { console.log('myThing:', myThing); });</code></pre>
评论 #3511898 未加载
评论 #3513209 未加载
phzbOxover 13 years ago
I rarely use ifs and whiles using javascript.. There are better, higher level libraries that take care of it for you. As a bonus, some of them let you make it * paralleled* with the same syntax. Obviously, when you switch to a new language, you need to learn their new paradigms / designs.
评论 #3512386 未加载
评论 #3511501 未加载