ruk·si

🟢 Node
Non-blocking Code

Updated at 2015-07-25 16:41

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);
}

Sources