2012年3月16日 星期五

Node.js Callback Function 不成文的習慣

Standard
我們常會在一些 Node.js 或是第三方模組的 API 上,看到 callback function 的第一個參數是 err 的情況,雖然官方並沒有明文規定(或許有,只是我沒有看到?),但這樣的習慣已經隨處可見。從官方 API 文件中的範例,就能看到許多例子:
fs.rename('/tmp/hello', '/tmp/world', function (err) {
  if (err) throw err;
  console.log('renamed complete');
});

fs.stat('/tmp/world', function (err, stats) {
  if (err) throw err;
  console.log('stats: ' + JSON.stringify(stats));
});

這樣的情況,最常出現在非同步(asynchronous)執行的函式中。因為這類的函式呼叫,做法是將工作丟到背景等待完成,所以會立即回傳(return),並繼續執行下面的程式。而該函式的工作,在真正完成或有出錯時,會呼叫 callback function。所以,在 callback function 內,我們需要藉由判斷 err 存在與否,去得知該函式是否完成工作或出錯。

至於 err 是什麼格式,對大多開發者來說,他只是個純字串的存在,也就是你可以直接印出它的內容:
console.log(err)

但事實上,err 是 Error 物件,這是 ECMA 有定義的標準物件,對一些有經驗的 JavaScript 開發者來說,應該不陌生,甚至除了 Node.js 之外,在其他的瀏覽器上都有支援。如果想知道他是什麼東西,也可以自己嘗試建立一個 Error 物件來觀察:
var err = new Error('Hello Error!');

比較有趣的是,Error 物件被建立時,會包括 JavaScript Engine Stack 的資訊,這有利於更進一步的 Debug 工作。
console.log(err.stack)

Error: Hello Error!
    at repl:1:6
    at REPLServer.eval (repl.js:80:21)
    at Interface. (repl.js:182:12)
    at Interface.emit (events.js:67:17)
    at Interface._onLine (readline.js:162:10)
    at Interface._line (readline.js:426:8)
    at Interface._ttyWrite (readline.js:603:14)
    at ReadStream. (readline.js:82:12)
    at ReadStream.emit (events.js:88:20)
    at ReadStream._emitKey (tty.js:327:10)

所以,如果你正在設計一個擁有 callback 的函式,比較好的做法就是遵循這樣有 Error 物件的設計模式。
function waitingforyou(callback) {
    /* Do something */
    data = doSomething();

    if (!data)
        callback(new Error('Something\'s Wrong'));
    else
        callback(null, data);
}

後記

JavaScript 有太多彈性可以讓我們把他搞的亂七八糟,如果不是故意要惡搞,建立一些設計習慣,才是好的選擇。