After having spent a month or two trying to learn JavaScript, especially functional programming, async, and closures, I finally get it. But I don't know if I'm writing elegant code... Specifically, I'm not sure if I'm creating too many closures, because I don't entirely grasp when contexts remain and when they're marked for garbage collection.
You can probably tell from the example that I'm using node.js -- basically, this is just a piece of test code that connects to a database, gets 20,000 records asynchronously with individual SELECT
queries (I'm just doing some profiling, don't worry), and manipulates the data. At the end, it tells me how long it took and closes the MySQL connection.
In order to get a final timestamp and close the connection, I needed something like the async
library to keep track of all the various asynchronous functions that are being spawned. It takes all my queries as an array of functions (using a factory called makeQuery()
) and then runs a callback to do the cleanup.
Questions:
Am I creating a huge amount of contexts by creating and returning the function
doQuery()
which uses the argumentid
in themakeQuery()
function, thereby causing 20,000 contexts to persist untilasync.parallel()
is run?Is the factory function
makeQuery()
a good and tidy way to create this array ofdoQuery()
functions? Can you suggest any better way to do it?
var mysql = require('mysql');
var radix64 = require('./lib/radix64');
var async = require('async');
var client = mysql.createClient({user: 'root', password: '12345678'});
client.query('USE nodetest');
var startTime = (new Date()).getTime(); // poor man's profiling
var queries = []; // this will be fed to async.parallel() later
var makeQuery = function makeQuery (id) { // factory function to create the queries
return function doQuery (cb) {
client.query('SELECT * FROM urls WHERE id='+id,
function logResults (e, results, fields) {
results = results[0];
results.key = radix64.fromNumber(results.id); // yep, I'm converting the
// number into base-64 notation
cb(); // if I wanted to do something useful at the end, I would have
// called cb(results) instead, which compiles an array of results
// to be accessed by the final callback
}
);
};
};
for (i = 1001; i <= 20000; i ++) { // build the list of tasks to be done in parallel
queries.push(makeQuery(i));
}
// run those tasks
async.parallel(queries, function finished () { // clean up and get the elapsed time
console.log('done');
client.end();
console.log(((new Date()).getTime() - startTime) / 1000);
});
For those of you unfamiliar with async
, it's a userland module built for node.js and the browser. Each function in the array that gets passed to async.parallel()
is obliged to take a callback and then run that callback once it's done, in order to let async.parallel()
know it's done.