Permalink
Browse files

Optimized to GFM

  • Loading branch information...
1 parent ac10819 commit 8f75f0f1a2bc5c8a62e0f1f0f1b60d625df95667 @pgte pgte committed Aug 10, 2012
Showing with 79 additions and 67 deletions.
  1. +79 −67 article.md
View
@@ -6,18 +6,21 @@ Some patterns are hard to grasp, specially when programming asynchronously like
If you had to write this in a synchronous fashion you would do something like this:
- function insertCollection(collection) {
- for(var i = 0; i < collection.length; i++) {
- db.insert(collection[i]);
- }
- }
+```javascript
+function insertCollection(collection) {
+ for(var i = 0; i < collection.length; i++) {
+ db.insert(collection[i]);
+ }
+}
+```
## Parallel Execution
Since we are using Node, `db.insert` performs I/O and is therefore asynchronous. We have to turn this into an asynchronous function.
This following implementation is __wrong__:
+```javascript
function insertCollection(collection, callback) {
for(var i = 0; i < collection.length; i++) {
db.insert(collection[i], function(err) {
@@ -26,75 +29,82 @@ This following implementation is __wrong__:
}
callback();
}
+```
The problem with this one is obvious: `callback` is called right after launching all the `db.inserts` on the background, not leaving them a chance to finish. By the time callback gets called, none of the inserts has terminated.
Another approach (__which is also wrong__) would be this one:
- function insertCollection(collection, callback) {
- for(var i = 0; i < collection.length; i++) {
- (function(i) {
- db.insert(collection[i], function(err) {
- if (err) { return callback(err); }
- if (i == (collection.length - 1)) {
- callback();
- }
- });
- })(i);
- }
- }
+```javascript
+function insertCollection(collection, callback) {
+ for(var i = 0; i < collection.length; i++) {
+ (function(i) {
+ db.insert(collection[i], function(err) {
+ if (err) { return callback(err); }
+ if (i == (collection.length - 1)) {
+ callback();
+ }
+ });
+ })(i);
+ }
+}
+```
There is some temptation to think *we have to call when the last insert calls back*, but this is plain wrong. The first insert can still be executing when the last one callsback. You never know the order in which I/O operations finish.
A safer approach is to do something like this:
- function insertCollection(collection, callback) {
- var inserted = 0;
- for(var i = 0; i < collection.length; i++) {
- db.insert(collection[i], function(err) {
- if (err) {
- return callback(err);
- }
- if (++inserted == collection.length) {
- callback();
- }
- });
+```javascript
+function insertCollection(collection, callback) {
+ var inserted = 0;
+ for(var i = 0; i < collection.length; i++) {
+ db.insert(collection[i], function(err) {
+ if (err) {
+ return callback(err);
}
- }
+ if (++inserted == collection.length) {
+ callback();
+ }
+ });
+ }
+}
+```
You should only callback when __all__ of the inserts have terminated.
This approach is still unsafe, since there is the chance that, if an error occurs, the `callback` function gets called more than once. We should put a guard against that.
Also, we are calling an external function that may throw an error synchronously: we better also guard agains that. A good approach is to centralize the control on a function we can name `done`:
- function insertCollection(collection, callback) {
- var inserted = 0,
- terminated = false;
-
- function done() {
- if (! terminated) {
- terminated = true;
- callback.apply({}, arguments);
+```javascript
+function insertCollection(collection, callback) {
+ var inserted = 0,
+ terminated = false;
+
+ function done() {
+ if (! terminated) {
+ terminated = true;
+ callback.apply({}, arguments);
+ }
+ }
+
+ for(var i = 0; i < collection.length; i++) {
+ try {
+ db.insert(collection[i], function(err) {
+ if (err) {
+ return done(err);
}
- }
-
- for(var i = 0; i < collection.length; i++) {
- try {
- db.insert(collection[i], function(err) {
- if (err) {
- return done(err);
- }
- if (++inserted == collection.length) {
- done();
- }
- });
- } catch(err) {
- done(err);
+ if (++inserted == collection.length) {
+ done();
}
-
- }
+ });
+ } catch(err) {
+ done(err);
}
+
+ }
+}
+```
This `done` function assures that we are not invoking the `callback` function more than once.
@@ -104,20 +114,22 @@ Sometimes you may need to control the flow and / or the order of the execution.
If that's the case, you can do something like this:
- function insertCollection(collection, callback) {
- var coll = collection.slice(0); // clone collection
-
- (function insertOne() {
- var record = coll.shift(); // get the first record of coll and reduce coll by one
- db.insert(record, function(err) {
- if (err) { return callback(err); }
- if (coll.length == 0) {
- callback();
- } else {
- insertOne();
- }
- }
- })();
+```javascript
+function insertCollection(collection, callback) {
+ var coll = collection.slice(0); // clone collection
+
+ (function insertOne() {
+ var record = coll.shift(); // get the first record of coll and reduce coll by one
+ db.insert(record, function(err) {
+ if (err) { return callback(err); }
+ if (coll.length == 0) {
+ callback();
+ } else {
+ insertOne();
+ }
}
+ })();
+}
+```
Here we are declaring the function `insertOne` and then invoking it immediately after. When an insert is finished we call it again unless we don't have anything else to insert, in which case we callback saying the operation terminated.

0 comments on commit 8f75f0f

Please sign in to comment.