Eric Schoffstall
I like to code.
Consulting, training, products, professional services
contact@wearefractal.comOpen source is at github.com/wearefractal
I/O (http, fs, db, etc.) happens in a thread pool
Application code happens in a single JS thread with an event loop
As tasks in the I/O thread pool complete, callbacks are executed in the main thread
As tasks in the main thread complete, another task is popped off and executed
Only one block of code is being executed in the main thread at any time
Naming: cb (short for callback)
var getUser = function(cb){
// TODO: fetch a user from mongodb
};
Arguments: Error first, data second
getUser(function(err, user){
});
Error management: Use domains
var d = domain.create();
d.on('error', function(err){
console.log('OMG!', err);
});
getUser(d.intercept(function(user){
}));
fs.readdir(source, function(err, files) {
if (err) {
console.log('Error finding files: ' + err);
} else {
files.forEach(function(filename, fileIndex) {
console.log(filename);
gm(source + filename).size(function(err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
console.log(filename + ' : ' + values)
aspect = (values.width / values.height)
widths.forEach(function(width, widthIndex) {
height = Math.round(width / aspect)
console.log('resizing ' + filename + 'to ' + height + 'x' + height);
this.resize(width, height).write(destination + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err);
});
}.bind(this))
}
});
});
}
});
fs.readdir(source, function(err, files) {
if (err) return console.log('Error finding files:', err);
files.forEach(function(filename, fileIndex) {
console.log(filename);
gm(source + filename).size(function(err, values) {
if (err) return console.log('Error identifying file size:', err)
console.log(filename, ':', values)
aspect = (values.width / values.height)
widths.forEach(function(width, widthIndex) {
height = Math.round(width / aspect)
console.log('resizing', filename, 'to', width+'x'+height);
this.resize(width, height).write(destination + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err);
});
}.bind(this));
});
});
});
function getImageRatio(image, cb) {
image.size(function (err, values) {
if (err) return cb(err);
var aspect = (values.width / values.height);
cb(null, aspect);
});
}
function resizeFile(filename) {
var image = gm(filename);
getImageRatio(image, function (err, aspect) {
if (err) return cb(err);
widths.forEach(function (width, widthIndex) {
var height = Math.round(width / aspect);
var outputFile = filename + '-' + width;
image.resize(width, height).write(outputFile);
});
});
}
fs.readdir(source, function (err, files) {
if (err) return console.log('Error finding files:', err);
files.forEach(resizeFile);
});
var async = require('async');
function getImageRatio(image, cb) {
image.size(function (err, val) {
if (err) return cb(err);
var aspect = (val.width / val.height);
cb(null, aspect);
});
}
function resizeImageWithAspect(filename, image, aspect, width, cb) {
var height = Math.round(width / aspect);
var outputFile = filename + '-' + width;
image.resize(width, height).write(outputFile, cb);
}
function resizeImage(filename, cb) {
var image = gm(filename);
getImageRatio(image, function (err, aspect) {
if (err) return cb(err);
var fn = resizeImageWithAspect.bind(null, filename, image, aspect);
async.forEach(widths, fn, cb);
});
}
fs.readdir(source, function (err, files) {
if (err) return console.log('Error finding files:', err);
async.forEach(files, resizeImage, function (err) {
if (err) return console.log('Error resizing images:', err);
console.log('Finished!');
});
});
async.map(['file1','file2','file3'], fs.stat, function(err, results){
// results is now an array of stats for each file
});
async.filter(['file1','file2','file3'], fs.exists, function(results){
// results now equals an array of the existing files
});
async.parallel([
function(){ ... },
function(){ ... }
], callback);
async.series([
function(){ ... },
function(){ ... }
]);
async.each([1,2,3], calc, function(err){});
Will schedule all tasks immediately
async.eachSeries([1,2,3], calc, function(err){});
Will schedule each task one by one as they complete
git clone https://github.com/wearefractal/async-node-workshop
cd async-node-workshop
npm install
// gets a user from the db
// and calls back with (err, user) signature
var getUser = function(name, cb) {
// simulate async
setTimeout(function(){
cb(null, {name: name});
}, 100);
};
var person = "Mary";
// Get marys user object and console.log it
// gets a user from the db
// and calls back with (err, user) signature
var getUser = function(name, cb) {
// simulate async
setTimeout(function(){
cb(null, {name: name});
}, 100);
};
var person = "Mary";
// Get marys user object and console.log it
getUser(person, function(err, user){
console.log(user);
});
var async = require('async');
var fs = require('fs');
// gets a user from the db
// and calls back with (err, user) signature
var saveUser = function(name, cb) {
var fileName = name + ".txt";
var content = name + " is cool!";
fs.writeFile(fileName, content, cb);
};
var people = ["Mary", "Todd", "Mike"];
// For each person in the people array
// Call saveUser
// Use async.each
var async = require('async');
var fs = require('fs');
// gets a user from the db
// and calls back with (err, user) signature
var saveUser = function(name, cb) {
var fileName = name + ".txt";
var content = name + " is cool!";
fs.writeFile(fileName, content, cb);
};
var people = ["Mary", "Todd", "Mike"];
// For each person in the people array
// Call saveUser
// Use async.each
async.each(people, saveUser, function(err) {
});
var async = require('async');
// gets a user from the db
// and calls back with (err, user) signature
var getUser = function(name, cb) {
// simulate async
setTimeout(function(){
cb(null, {name: name});
}, 100);
};
var people = ["Mary", "Todd", "Mike"];
// Map the people array to getUser
// Using async.map
// Log the new array when you are done
var async = require('async');
// gets a user from the db
// and calls back with (err, user) signature
var getUser = function(name, cb) {
// simulate async
setTimeout(function(){
cb(null, {name: name});
}, 100);
};
var people = ["Mary", "Todd", "Mike"];
// Map the people array to getUser
// Using async.map
// Log the new array when you are done
async.map(people, getUser, function(err, users) {
console.log(users);
});
var async = require('async');
var redis = require('redis');
var client = redis.createClient();
// gets a user from the db
var getUser = function(name, cb) {
// simulate async
setTimeout(function(){
var val = name + " is cool!";
cb(null, {name: name, val: val});
}, 100);
};
// write user into redis
// and calls back with (err, user) signature
var writeUser = function(user, cb) {
console.log("Writing", user.name);
client.set(user.name, user.val, cb);
};
var people = ["Mary", "Todd", "Mike"];
// Map the people array to getUser
// Using async.map and async.each
var async = require('async');
var redis = require('redis');
var client = redis.createClient();
// gets a user from the db
var getUser = function(name, cb) {
// simulate async
setTimeout(function(){
var val = name + " is cool!";
cb(null, {name: name, val: val});
}, 100);
};
// write user into redis
// and calls back with (err, user) signature
var writeUser = function(user, cb) {
console.log("Writing", user.name);
client.set(user.name, user.val, cb);
};
var people = ["Mary", "Todd", "Mike"];
// Map the people array to getUser
// Using async.map and async.each
async.map(people, getUser, function(err, users){
async.each(users, writeUser, function(err){
console.log('Done!');
});
});
Before:
async.map(people, getUser, function(err, users){
async.each(users, writeUser, function(err){
console.log('Done!');
});
});
After:
async.waterfall([
function(done){
async.map(people, getUser, done);
},
function(users, done){
async.each(users, writeUser, done);
}
], function(err){
console.log('Done!');
});
Parsing Twitter:
getTweetsFor("domenic") // promise-returning async function
.then(function (tweets) {
var shortUrls = parseTweetsForUrls(tweets);
var mostRecentShortUrl = shortUrls[0];
return expandUrlUsingTwitterApi(mostRecentShortUrl); // promise-returning async function
})
.then(doHttpRequest) // promise-returning async function
.then(
function (responseBody) {
console.log("Most recent link text:", responseBody);
},
function (error) {
console.error("Error with the twitterverse:", error);
}
);
Parsing JSON:
inputStream
.pipe(JSONStream.stringify())
.pipe(outputStream);
By Eric Schoffstall
Best practices, musings, etc.