2012年12月23日 星期日

解決Node.js非同步問題... node-promise模組的使用


Node.js的優點:Event Driven, Non-Block非常強悍,但是同時也造成許多寫作上的不便...
這邊主要想解決在非同步的模組中,想要循序執行的問題... node-promise是不錯的解決方案

安裝:
npm install node-promise

情境:
希望可以在抓取一個網頁完成之後,再執行某個動作...
語意上可以翻譯做:當抓取網頁Event完成後,才去驅動下一個Event

使用:
var p = new require("node-promise").Promise()
  , request = require('request'); 
function step1(fn){ //包裝request的動作
  request({
    url : "http://www.google.com",
    method : "GET"
  },
  function(e,r,d){
    console.log('>>1');
    p.resolve(d);  // 註冊當動作完成時候的Listener
  });
}
step1();  // 執行step1()
p.then( //當動作完成時候,即會執行then所定義的動作
  function(result){ //定義收取結果後的操作
    console.log('>>2');
    console.log(result.substring(0,50));
  },
  function(err){ //定義收到錯誤時候的操作
    console.log(err);
  }
)

執行結果:
# node test-promise
>>1
>>2  // 2 會在 1 之後執行
<!doctype html><html itemscope="itemscope" itemtyp  // 最後列出前request結果的50個字元

除了promise,該套件另外有提供defered與when的操作,可以讓流程更加豐富...
但是,說實在的...整個操作並沒有簡化到哪邊去... 甚至比較麻煩...
不過,透過promise的定義,對event附加Listener的方式倒是可以解決許多callback操作的問題...
還是個值得多多了解的套件!


callee and caller

簡介一下callee與caller
  • callee為function arguments的一個操作,用來代表function本身
  • caller為function呼叫者,若為最上層呼叫
下面是簡單的操作,將callee與caller的值列印出來:

由fn2操作fn1,最上層呼叫直接呼叫fn2

function fn1(){
  console.log('>>fn1');
  console.log('my callee:'+arguments.callee);
  console.log('my caller:'+fn1.caller);
}
function fn2(){
  console.log('>>fn2');
  fn1();
}
fn2();

執行結果:
# node test 
>>fn2
>>fn1
my callee:function fn1(){
  console.log('>>fn1');
  console.log('my callee:'+arguments.callee);
  console.log('my caller:'+fn1.caller);
}
my caller:function fn2(){
  console.log('>>fn2');
  fn1();
}


如果想要測試最上層直接呼叫fn1的狀況,修改程式碼如下:

function fn1(){
  console.log('>>fn1');
  console.log('my callee:'+arguments.callee);
  console.log('my caller:'+fn1.caller);
}
fn1();

則結果為:
# node test
>>fn1
my callee:function fn1(){
  console.log('>>fn1');
  console.log('my callee:'+arguments.callee);
  console.log('my caller:'+fn1.caller);
}
my caller:function (exports, require, module, __filename, __dirname) { function fn1(){
  console.log('>>fn1');
  console.log('my callee:'+arguments.callee);
  console.log('my caller:'+fn1.caller);
}
fn1();

}

其中callee的應用,可以用在loop的程式中,強迫程式同步進行...
ex:
var count = 5;
process.nextTick(function fn1(){
  count --;
  console.log('>>fn1, ts=%s, count=%s', new Date().getTime(), count);
  if(count > 0) process.nextTick(arguments.callee);
});

執行結果:
# node test 
>>fn1, ts=1356282477274, count=4
>>fn1, ts=1356282477280, count=3
>>fn1, ts=1356282477281, count=2
>>fn1, ts=1356282477281, count=1
>>fn1, ts=1356282477281, count=0

這邊的output就可以看得出來function fn1會依序被執行,如果有非同步動作,則可以在callback中使用同樣手法讓程序變成循序執行的...

另外,caller的應用,直覺想到的是debug...
寫個簡單的log程式如下,希望可以記錄錯誤是由哪個function發出來的:
var log = {
  info: function fn(msg){
    console.log("Call from [%s]: %s", fn.caller.name , msg);
  }
}
function doSomething() {
  log.info('test');
}
doSomething();


執行結果:
node test
Call from [doSomething]: test



2012年12月21日 星期五

在Bash中混用Node.js程式

一般使用Node.js寫CLI或是使用Bash寫Server端腳本,都是單獨使用
頂多把JS包成完整的指令來使用...
但是,其實您可以透過node -e的方式來動態將Bash的參數輸入Node.js...
下面這段程式只是demo怎麼把Bash中的參數傳入Node.js中做執行,而且,不用再另外開一個JS檔案...

檔案:[test.sh]
#!/bin/bash                                                                                                                                                                                            
export UNAME=`uname -a`
node -e "
  var uname = '$UNAME';
  var arr = uname.split(' ');
  for(var i = 0 ; i< arr.length ; i++){
    console.log(arr[i])
  }
"

執行:
# bash test.sh
 
以上,簡單的程式可以混用Bash與Node.js :D

使用Node接收Shell pipe資料做處理

撰寫command line程式時候,常常需要處理pipe的問題
而Node.js具備命令列執行的特色,可以作為腳本語言的撰寫工具
但是,如何在Node.js處理Shell pipe過來的資料呢?
答案是使用process.openStdin()...
下面是範例片段:

程式碼:[test.js]
#!/usr/bin/env node
var buffer = '';
var stdin = process.openStdin();
stdin.setEncoding('utf8');
stdin.on('data', function (chunk) {
    buffer += chunk;
});
stdin.on('end', function () {
  console.log(buffer);
});

修改執行權限:

# chmod u+x test.js

測試
# echo HELLO | ./test.js

結果... 自己試試 :D
 

2012年12月11日 星期二

Using log4js in your project...

永遠的Console Log不會是一個聰明程式設計師的選擇
Node.js的一套log套件,可以讓程式裡面的Log機制表達更完整
他包含可以針對log分等級、分類、建立不同形式的log(console, file, mail...)、幫你切割log大小、幫你House Keeping...
其中等級部分:

TRACE > DEBUG > INFO > WARN > ERROR > FATAL

其中,TRACE為最囉唆模式,顯示的LOG會包含Debug, Info, Warn, Error, Fatal等,越往右邊,所包含顯示項目越少...
下面是使用上的一些紀錄:

Install:
# npm install log4js
npm http GET https://registry.npmjs.org/log4js
npm http 304 https://registry.npmjs.org/log4js
npm http GET https://registry.npmjs.org/async/0.1.15
npm http 304 https://registry.npmjs.org/async/0.1.15
log4js@0.5.6 ../node_modules/log4js
└── async@0.1.15


File: logger.js 
/**
 * Usage: 
 * var logger = require('./logger').getInstance();
 * logger.debug('TEST...123');
 */
var log4js = require('log4js')
  , logger = log4js.getLogger()
  , logFile = process.env.LOGPATH ? process.env.LOGPATH : '/tmp/node.log'
  , logCategory = process.env.LOGCATG ? process.env.LOGCATG : 'normal'
  , logLevel = process.env.LOGLEVEL ? process.env.LOGLEVEL : 'DEBUG'
  , logMaxSize = process.env.LOG_MAX_SIZE ? process.env.LOG_MAX_SIZE : 20480
  , logBackup = process.env.LOG_BACKUP ? process.env.LOG_BACKUP : 7;
log4js.configure(
{
  "appenders": [
    { type: 'console' },
    {
      "type": "file",
      "filename": logFile,
      "maxLogSize": logMaxSize,
      "backups": logBackup,
      "category": logCategory
    }
  ]
}
);
var logger = log4js.getLogger(logCategory);
logger.setLevel(logLevel);
exports.getInstance = function() {
  return logger;
}

File: test.js
var logger = require('./logger').getInstance();
logger.trace('[trace]....');
logger.debug('[debug]
....');
logger.info('[info]
....');
logger.warn('[warn]
....');
logger.error('[error]
....');
logger.fatal('[fatal]
....'); 

執行上,針對不同等級,有使用不同顏色來表示,上面程式將log以環境變數來設定,如果設定上環境變數,就可以清楚知道Log的等級:

Trace mode:


Debug mode:

Info mode:


Warn mode: 


Error mode:


Factal mode:



2012年12月6日 星期四

MySQL v2.x connector for Node.js

Node.js連線MySQL部分,目前我常用的是mysql這個module
可透過npm install mysql取用...

而最近2.x版本釋出後,發現連線方式有修改了:

var db_options = { 
    host: 'your.database.ip.address',
    port: 3306,
    user: 'username',
    password: 'password',
    database: 'db_name'
};

/* mysql@2.0.0-alpha4連線設定 */
var mysql = require('mysql')


exports.db = mysql.createConnection(db_options);       

如果專案中不確定會用到哪個版本時候
可以這樣寫:

if ( mysql.createClient ) { //v0.9.x
  db = mysql.createClient(db_options);
} else if ( mysql.createConnection ){ //v2.x
  db = mysql.createConnection(db_options);
} else { //more early
  db = new mysql.Client(db_options);
  db.connect(function(err) {
      if ( err ) {
          console.error('connect db ' + db.host + ' error: ' + err);
          process.exit();
      }
  });
}