Asynchronous Iteration Patterns (in Node.js)
metaduck.com
metaduck.com
In languages that are not fundamentally synchronous, such as Erlang, you do not leap through hoops to manage this. You simply write a function that performs your insertions in the straightforward and obvious way, and the runtime manages it with no blocking at any point.
I actually don't like the frequently-made assertion that "a pattern is automatically a weakness in the language", but it does apply here. As the Node.js community laboriously builds up the patterns necessary to work in this paradigm, recapitulating the work done in numerous other async-on-top-of-sync-language libraries, it probably is worth keeping the assertion in mind. You shouldn't even have to think about this, let alone argue which way is the best way to do it.
(Somebody modded this down. Maybe I should make it clear that I'm actually speaking from experience on another asynchronous project written in Perl on top of glib's asynchronousness, which isn't fundamentally different from Node.js'. I'm not speculating, I've been on the receiving end of this complexity explosion. It will happen.)
Count me in. Been there with Twisted and (partly) EventMachine.
Imho node should really abstract this away before the eco-system turns into a giant spaghetti ball. Of course you can't bend javascript into a concurrency model as elegant as erlang's. But co-routines or a similar abstraction is urgently needed at least for the code that the users end up writing.
On a related note; I recently switched to coffeescript in an effort to bring my (still small) node codebase back into a half-way readable state.
The simple act of removing most curly brackets had an astonishing impact on readability. I still have to reason about callback chains, but at least I no more have to wade through half screens full of closing brackets while doings so.
That observation was a bitter-sweet reminder of what I'm (willfully) getting myself into with node - all the while keeping my fingers crossed that this wart on an otherwise beautiful environment will be improved asap.
Of course, it didn't have to be that way if they had proper event triggering semantics. I find very little hope of the community returning to this issue though. Not that it's the only way to solve things... but it would have been a nice balance IMO. (and I still hold that coroutines can be done cheaply... at least as cheaply as event sources if not more)
(Update: to clarify on proper coroutines on node, each needs to be able to iterate on an independent tick loop rather than allow preemption from EventEmitter#emit)
Solutions that make this transparent almost always return to heavy constructs that people like Ryan Dahl called out as big scaling problems (like threads). Just shoveling state around is a mess and abstractions around it almost always reinvent the smalltalk-style spaghetti stack.
I'm not sure node.js has a clear option moving forward. Right now the library choice for this is pretty wide, so fragmentation is a problem. While it's hard to choose sometimes, right now the community really needs some direction.
Meanwhile, languages and platforms that have already chosen their concurrency primitives, be it goroutines and channels, erlang processes and messages, or even ugly threads and locks, seem to be moving forward. It's not about defeating some other method of expressing things. It's about providing something that works and right now node.js only solves half of the problem.
function insertCollection(collection) {
for(var i = 0; i < collection.length; i++) {
db.insert(collection[i]);
}
}
To this: function insertCollection(collection, callback) {
var coll = collection.slice(0); // clone collection
(function insertOne() {
var record = coll.splice(0, 1)[0];
try {
db.insert(record, function(err) {
if (err) { callback(err); return }
if (coll.length == 0) {
callback();
} else {
insertOne();
}
}
} catch (exception) {
callback(exception);
}
})();
}
Hopefully this article will work as an eye-opener for anyone who was not yet convinced that node badly needs a concurrency abstraction.(Edit: noticed that the article does it this way.)
How about just:
function insertCollection(collection, cb) {
if (collection.length === 0) cb(null);
else db.insert(collection[0], function (err) {
if (err) cb(err)
else insertCollection(collection.slice(1), cb)
}
}
That's hardly much more complex, and as a bonus won't lock up your whole program while db.insert() is working. You could use something like my own library Seq() too: var Seq = require('seq');
function insertCollection (collection, cb) {
Seq.ap(collection).seqEach(function () {
db.insert(c, this);
}).seq(cb).catch(cb)
}
That is, if you actually want the inserts to go sequentially. Usually you want them to go in parallel with say a maximum of 10 pending requests: var Seq = require('seq');
function insertCollection (collection, cb) {
Seq.ap(collection).parEach(10, function () {
db.insert(c, this);
}).seq(cb).catch(cb)
}
I don't see any "fundamental issue" with node here. It's just a very new ecosystem and the idioms and libraries are rapidly evolving.That's hardly much more complex
Well, sorry to be snarky, but you couldn't have supported my point better.
Both you and the article author (where the code snippets are from) missed this critical detail during the first iteration. And not on some complex, esoteric problem but on one of the most basic language constructs.
I blame neither of you, I only ask that we please not discount the extra complexity.
Quite frankly, I've been down that rabbit hole with Twisted which also has most of these primitives. More magic does not help!
I do not want to deal with 'Future-ish object for the purpose of synchronizing other Futures'. Just let me write my code in sequential order where appropriate - which is about 90% of the business logic.
I'll quote jerf from this thread: You shouldn't even have to think about this, let alone argue which way is the best way to do it.
And Larry Wall: The computer should be doing the hard work. That's what it's paid to do, after all.
This is awesome that you wrote a post on this, it is an important building block to proper node.js coding. I know I had trouble with it.
There is another way to handle asynchronous iteration, while similar to what you have done, gives a bit more flexibility and is abstracted out so you can use it anywhere.
https://gist.github.com/b5af7369ec9939ab7d94
This is using code from a script of mine as an example, but it should be pretty easy to see the pattern.
Here is your above serialized example with the above function I just showed you:
https://gist.github.com/26bda51358610667f9f3
---
On a side note, I wanted to say that people hell bent on turning node.js into another language should just use that language. There is nothing wrong with the way node.js handles asynchronous code, and really keeping everything asynchronous instead of building in stuff to fake synchronous code is bad for node.js and the whole ecosystem. It promotes bad habits and bad code. If you can't handle callbacks or events, then maybe you should go back to using whatever you are more comfortable with, or write your own abstractions.
I know people like node.js and want to make it better in their own way, but I feel the course it is on is the best course for right now. It might add more cruft for developers, but there are plenty of abstractions that make it simple if you choose to.
@defer.inlineCallbacks
def insertCollection(collection):
for ob in collection:
yield db.insert(ob)
...as much as people talk about the complexity of twisted with respect to async programming, they've really figured it out over the many years of the life of the project.I agree with substack that you can do this without changing the language, but you do have to get something usable to people at some point.
I think most people who write any code in node.js run into this issue. I am a casual user, but ran into it a couple of days ago and did some research to find that there are many third-party modules I could bring in to do a for loop, but nothing built-in. The old way of doing this in twisted (manually managing deferreds) really turned a lot of people off. By the time twisted got inlineDeferreds and inlineCallbacks, people already had their impressions of how difficult things are.
I still think node.js is fun and viable, but it does need a for loop.
Correct me if I'm wrong, but I don't think he means tail recursion. It's recursion, yes. But does tail recursion even make sense in an asynchronous setting?
http://groups.google.com/group/nodejs/browse_thread/thread/c...
Also, that guy is responsible for nodetuts, which I'm very thankful for!