Skip to content

Instantly share code, notes, and snippets.

@espadrine
Created January 19, 2012 13:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save espadrine/1640136 to your computer and use it in GitHub Desktop.
Save espadrine/1640136 to your computer and use it in GitHub Desktop.
Array Processing in Event Loops
var async = require('async');
function differedFactorial(n /* Number */, cb /* Function */) {
if (n < 0) cb(new Error('Complex infinity'));
setTimeout(function() {
var result = 1;
for (; n > 1; n--) {
result = result * n;
}
cb(null, result); // No errors, result is given.
}, 150 + Math.abs(300 * Math.random()));
}
var a = [2, 4, 6, 9];
async.map(a, differedFactorial, function(err, res) {
console.log(res);
});
// The following function is a dummy example of
// a function that is run in the next event loop cycle.
// A non-dummy example would be, for instance, an array
// of file names (as Strings), for which we want the contents.
// In node.js, reading from a file is non-blocking;
// it waits for the next cycle.
function differedFactorial(n /* Number */, cb /* Function */) {
if (n < 0) cb(new Error('Complex infinity'));
setTimeout(function() {
var result = 1;
for (; n > 1; n--) {
result = result * n;
}
cb(result);
}, 300);
}
// We want to apply our fellow differedFactorial function.
// However, that function spans across multiple event loop cycles.
// As a result, the following function, which would work otherwise,
// does not return the expected result of [2, ...].
//
// Just like with coroutines, functions that span across EL cycles
// are unnoticeably "impure" for we cannot tell how they behave from
// how they look. Only the documentation can say.
var a = [2, 4, 6, 9];
console.log(a.map(function(e) {
var result;
differedFactorial(e, function(res) {
result = res;
});
return result;
}));
// The only way out is to use this function.
// A major contract we make with the user is that he *must*
// run the callback `cb` for each element passed in `f`.
// Otherwise, the whole thing never yields a result.
Array.prototype.asyncMap = function(f /* Function */, cb /* Function */) {
var l = [], len = this.length;
for (var i = 0; i < len; i++) {
f(this[i], i, this, function(e) {
l.push(e);
if (l.length === len) {
cb(l);
}
});
}
};
// Notice how that's actually a map we reimplement in there?
// Let's make use of the version of `map` that doesn't return anything.
Array.prototype.asyncMap = function(f /* Function */, cb /* Function */) {
var l = [], len = this.length;
this.forEach(function(v, i, a) {
f(v, i, a, function(e) {
l.push(e);
if (l.length === len) {
cb(l);
}
});
});
};
a.asyncMap(function(e, i /* Number */, a /* Array */, cb /* Function */) {
differedFactorial(e, function(res) { cb(res); });
}, function(result) {
console.log(result);
});
// This time, the dummy non-blocking next-cycle function
// probably won't return values in order.
// Indeed, it runs the callback after a random amount of time.
function differedFactorial(n /* Number */, cb /* Function */) {
if (n < 0) cb(new Error('Complex infinity'));
setTimeout(function() {
var result = 1;
for (; n > 1; n--) {
result = result * n;
}
cb(null, result); // No errors, result is given.
}, 150 + Math.abs(300 * Math.random()));
}
// The old implementation doesn't care about the order of what
// was given in the array. The implementation is simpler, but
// it doesn't act like `map`.
Array.prototype.asyncMap = function(f /* Function */, cb /* Function */) {
var l = [], len = this.length;
for (var i = 0; i < len; i++) {
f(this[i], i, this, function(e) {
l.push(e);
if (l.length === len) {
cb(l);
}
});
}
};
var a = [2, 4, 6, 9];
a.asyncMap(function(e, i /* Number */, a /* Array */, cb /* Function */) {
differedFactorial(e, function(res) { cb(res); });
}, function(result) {
console.log('asyncMap: %s', result);
});
// Again, the only way out is to use this function.
// A major contract we make with the user is that he *must*
// run the callback `cb` for each element passed in `f`, and
// he *must* give it the processed element
// *and* the associated index.
// Otherwise, the whole thing never yields a result.
Array.prototype.asyncOrderedMap = function(f /* Function */,
cb /* Function */) {
var processing = 0,
l = new Array(this.length),
len = this.length;
for (var i = 0; i < len; i++) {
f(this[i], i, this, function(e, idx) {
l[idx] = e;
processing++;
if (processing === len) {
cb(l);
}
});
}
};
a.asyncOrderedMap(function(e,
i /* Number */,
a /* Array */,
cb /* Function */) {
// The callback has one more argument, the index.
differedFactorial(e, function(res) { cb(res, i); });
}, function(result) {
console.log('asyncOrderedMap: %s', result);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment