🟢 Node - Non-blocking Code
Understand what is non-blocking code. To write good Node applications, you must understand difference between blocking vs non-blocking code. If you want to know why asynchronous is better, look for discussing and benchmarks about Apache vs NGINX.
Non-blocking Node code is mainly written using callbacks, promises, events and streams.
Callbacks
You should always provide callback interface to your public libraries as Node community expects that.
// bad
var fs = require('fs');
var contents = fs.readFileSync('index.html');
console.log(contents);
// good
var fs = require('fs');
fs.readFile('index.html', function(error, contents) {
console.log(contents);
});
Return after calling callbacks. Otherwise execution keeps on going.
// bad
function writeCsvFile(target, data, callback) {
convertToCsv(data, function(err, csv) {
if (err) {
callback(err);
}
// this line gets called even when theres an error
writeFile(target, csv, callback);
});
}
// good
function writeCsvFile(target, data, callback) {
convertToCsv(data, function(err, csv) {
if (err) {
callback(err);
return; // execution stops here if there is an error
}
writeFile(target, csv, callback);
});
}
Promises
var promise1 = $.get( '/ajax/translate', { key: 'word_username' } );
promise1.done(function(result) {
console.log('Promise 1 gave... ' + result);
});
promise1.fail(function() {
console.log('Promise 1 failed');
});
Events
var EventEmitter = require('events').EventEmitter;;
var chat = new EventEmitter();
chat.on('message', function(message){
console.log(message);
});
chat.emit('message', 'This is a message.');
Messaging
Using messaging with postal.js. Basically special case of events.
var xboxChannel = postal.channel("xbox");
var reactions = {
newgame: function(data, env) {
return "I hope this goes better than the last one did!";
},
newmedal: function(data, env) {
return "Nice, a new medal - about time!";
},
lostgame: function(data, env) {
return "Ouch, you got pwned!";
},
wongame: function(data, env) {
return "Stroke of luck, perhaps?";
}
};
var module = {
handleMessage: function(data, env) {
// if we have a reaction for this kind
// of message we'll call makeComment.
if (reactions[env.topic]) {
this.makeComment(reactions[env.topic](data, env));
}
},
makeComment: function(msg) {
$('body').append("<div>" + msg + "</div>");
}
};
xboxChannel.subscribe("#", module.handleMessage).withContext(module);
var chan = postal.channel("xbox");
var socket = io.connect(location.origin); // Using socket io.
_.each(['newgame', 'newmedal', 'lostgame', 'wongame'], function(event) {
socket.on(event, function(data){
chan.publish(event, data);
});
});
Streams
Learn to use streams. Seriously, they are important.
Using streams, data distributed over time. Streams are basically a special case of event emitters where data chunks are passed on intervals.
// Readable stream.
var fs = require('fs');
var rStream = fs.createReadStream('index.html');
rStream.on('data', function(chunk) {
console.log( chunk.toString() );
});
rStream.on('end', function() {
console.log('Finished!');
});
// Writable stream.
var wStream = fs.createWriteStream('about.html');
wStream.write('Hello');
wStream.write(' ');
wStream.write('World!');
wStream.end('Ended.');
// wStream.write('This would throw an error.');
// Use pipe to redirect readable stream to writable stream without management.
var fs = require('fs');
var file = fs.createReadStream('index.html');
file.pipe(process.stdout);
// You can handle backpressure manually with `pause` and `resume`.
var fs = require('fs');
var oldFile = fs.createReadStream('icon.png');
var newFile = fs.createWriteStream('icon-new.png');
oldFile.on('data', function(chunk) {
var bufferIsOk = newFile.write(chunk);
if ( !bufferIsOk ) {
oldFile.pause();
}
});
newFile.on('drain', function() {
oldFile.resume();
});
oldFile.on('end', function() {
newFile.end();
});
// Streaming a file as a response.
var fs = require('fs');
var http = require('http');
http.createServer(function(request, response) {
response.writeHead(200, {'Content-Type': 'image/png'});
var file = fs.createReadStream('icon.png');
file.pipe(response);
});
All Asynchronous Code
Avoid sloppy asynchronous code. Use async library.
// bad
if (true) {
callback();
}
fallback();
// good
if (true) {
callback();
return;
}
fallback();
Catch synchronous code exceptions inside asynchronous code. They cannot be caught if you let them fall through.
// bad
function readJson(filename, callback) {
fs.readFile(filename, function(err, content) {
if (err) {
return callback(err);
}
// uh-oh! this line might throw an exception if the content
// is not valid JSON
var data = JSON.parse(content.toString());
return callback(null, data);
});
}
// good
function readJson(filename, callback) {
fs.readFile(filename, function(err, content) {
if (err) {
return callback(err);
}
try {
var data = JSON.parse(content.toString());
}
catch (exception) {
return callback(exception);
}
return callback(null, data);
});
}
Do not change concurrency model depending on context. Usually done by mistake.
// bad
var cache = {};
function getRecord(id, callback) {
if (cache[id]) {
return cache[id]; // value is returned from cache without using callback
}
http.get('http://foo/' + id, callback);
}
// good
var cache = {};
function getRecord(id, callback) {
if (cache[id]) {
return process.nextTick(function () {
return callback(null, cache[id]);
});
}
http.get('http://foo/' + id, callback);
}