Node.js Best practices

| Comments

完整的ppt来源于Node.js Best practices,作者Felix Geisendörfer,需翻越

Callbacks

下面是关于解析json文件的示例代码:

var fs = require('fs);
function readJSON(path, cb){
  fs.readFile(path, 'utf8', function(err, data){
    cb(JSON.parse(data));
  }
}

显然,上面的代码没有处理异常情况,因此再加个异常处理逻辑上去:

var fs = require('fs);
function readJSON(path, cb){
  fs.readFile(path, 'utf8', function(err, data){
    if(err) return cb(err);
    cb(JSON.parse(data));
  }
}

还没有结束,我们还没考虑到文件内容不是json这种情况,会导致parse出现异常,因此再处理一下:

var fs = require('fs);
function readJSON(path, cb){
  fs.readFile(path, 'utf8', function(err, data){
    if(err) return cb(err);
    try{
      cb(JSON.parse(data));
    }catch(err){
      cb(err);
    }
  }
}

对于cb来说,仍然无法区分正常结果和异常内容,这个问题通常可以通过增加一个err参数来处理,如:

var fs = require('fs);
function readJSON(path, cb){
  fs.readFile(path, 'utf8', function(err, data){
    if(err) return cb(err);
    try{
      var json = JSON.parse(data);
    }catch(err){
      return cb(err);
    }
    cb(null, json);
  }
}

这个示例告诉我们,对于callback,需要时刻准备应付正常结果和异常情况的处理。

再来看看另外一个常见错误:

function readJSONFiles(files, cb){
  var results = {};
  var remaining = files.length;
  
  files.forEach(function(file){
    readJSON(file, function(err, json){
      if(err) return cb(err);
      
      results[file] = json;
      if(!--remaining) cb(null, results);
    }
  });
}

这里隐含了一个常见的场景:批量处理时,任意一个失败,及时退出。有时候可以用标识符,这里采用另外一种手法:重置回调方法

function readJSONFiles(files, cb){
  var results = {};
  var remaining = files.length;
  
  files.forEach(function(file){
    readJSON(file, function(err, json){
      if(err){
        cb(err);
        cb = function(){};
        return;
      }
      
      results[file] = json;
      if(!--remaining) cb(null, results);
    }
  });
}

Nested Callbacks

先看看一个恐怖的例子:

db.query('SELECT A ...', function(){
  db.query('SELECT B ...', function(){
    db.query('SELECT C ...', function(){
      db.query('SELECT D ...', function(){
      });
    });
  });
});

活生生就是一个怪物:),多层嵌套的回调不是很好的风格,我们需要一些流程控制的东西来辅助,例如Control Flow Libs:

var async = require('async');

async.series({
  queryA: function(next){
    db.query('SELECT A ...', next);
  },
  queryB: function(next){
    db.query('SELECT B ...', next);
  },
  queryA: function(next){
    db.query('SELECT C ...', next);
  }
  // ...
}, function(err, results){
  //...
});

像上面的代码,最明显的地方就是异常处理被完全隔离出来。如果要把代码分布到很多小方法里边的话,Node.js的确不是很容易做到

Exceptions

通常throw new Error(msg)可以让你的程序进行异常退出,并在控制台上输入错误信息和堆栈信息。

但有时候我们要考虑的是,一些未知的bug,例如下面一个有bug的示例:

function MyClass(){}

MyClass.prototype.myMethod = function(){
  setTimeout(function(){
    this.myOtherMethod();
  }, 10);
}

MyClass.prototype.myOtherMethod = function(){};

(new MyClass).myMethod();

我们可以采用Global Catch的方式:

process.on('uncaughtException', function(err){
  console.err('uncaught exception: ' + err.stack); 
});

更好的处理方式是:进程挂了,认栽了,到更高层面上去处理

process.on('uncaughtException', function(err){
  // You could use node-airbake for this
  sendErrorToLog(function(){
    // Once the error was logged, kill the process
    console.err(err.stack);
    process.exit(1);
  }
});

Deployment

比较初级的方式是采用node直接运行或在后台运行。老手可能会采用一个脚本来搞:

#! /bin/bash

while :
do
  node server.js
  echo "Server crashed!"
  sleep 1
done

专家级采用的方式可能是(借助成熟的集成工具):

#!upstart

description "myapp"
author "felix"

start on(local-filesystems and net-device-up IFACE=eth0)
stop on shutdown

respawn # restart when job dies
respawn limit 5 60 # give up restart after 5 respawns in 60 seconds

script
  exec sudo -u www-data /path/to/server.js >> /var/log/myapp.log 2>&1
end script

当然,还有些创新风格的(基于托管平台的):

$git push joyent master
$git push nodejitsu master
$git push heroku master

没有托管平台的话,借助成熟的工具应该是最好的选择,性价比高。

Comments