Node.js error-first callback


前言

"error-first"(錯誤優先)顧名思義 - 程式碼與參數都以"錯誤"的處理為最優先。

error-first callback也常被稱為“errorback”, “errback”或“node-style callback”

異步程式撰寫時,我們在函式裡在最後完成時,並不會作回傳(return)值的動作,而是交給callback持續執行下去,這種開發風格稱為 延續傳遞風格 Continuation-Passing Style, CPS)

在Node.js中的幾乎所有的內建函式與模組提供的函式,都是依這種風格與規則製作的函式。callback通常以匿名函式被傳入函式最後一個參數,以此在函式的完成時繼續呼叫執行。

定義規則

  • callback的第一個參數保留給錯誤(error)物件:當錯誤發生時,它將會以第一個參數回傳。

  • callback的第2個參數保留給成功回應的資料。當沒有錯誤發生時,error(即第一個參數)會設定為null,然後將成功回應的資料傳入第二個參數。

規則只有兩條,但實際上在callback裡還是要先處理錯誤,再執行所需的結果,也算是"錯誤優先",下面是兩個範例:

var dns = require('dns');

dns.resolve4('www.google.com', function (err, addresses) {
  // Error Handling Still Needed!
  if (err) throw err;

  console.log('addresses: ' + JSON.stringify(addresses));
});
var fs = require('fs');

fs.readFile('foo.txt', 'utf8', function(err, data) {
  // Error Handling Still Needed!
   if(err) {
    console.log('Unknown Error');
    return;
  }

  console.log(data);
});

錯誤處理

錯誤有很多不同的類型,有可能是預期中的,也有可能是嚴重的災難性錯誤。當錯誤發生時,你可以用判斷流程來分類處理它們,例如:

if(err) {
  // Handle "Not Found" by responding with a custom error page
  if(err.fileNotFound) {
    return this.sendErrorMessage('File Does not Exist');
  }
  // Ignore "No Permission" errors, this controller knows that we don't care
  // Propagate all other errors (Express will catch them)
  if(!err.noPermission) {
    return next(err);
  }
}

用Error類自訂錯誤的方式

使用Error類自訂錯誤的類型的範例(這個範例來自這裡,最後有一段是使用async模組的平行運算語法):

// The Asyncronous Method
function strictAddition(x, y, callback) {
  if(typeof x !== 'number') {
    callback( new Error('First argument is not a number') );
    return;
  }
  if(typeof y !== 'number') {
    callback( new Error('Second argument is not a number') );
    return;
  }
  var result = x + y;
  setTimeout(function() {
    callback(null, result);
  }, 500);
}

// The Callback
function callback(err, data) {
  if(err) {
    console.log(err);
    return;
  }
  console.log(data);
}

// Examples
strictAddition(2, 10, callback); // 12
strictAddition(-2, 10, callback); // 8
strictAddition('uh oh', 10, callback); // Error = "First argument is not a number"
strictAddition(2, '10', callback); // // Error = "Second argument is not a number"

// Async Example - all calls made in parallel
async.parallel({
    twelve: function(callback){ strictAddition(2, 10, callback); },
    fiftythree: function(callback){ strictAddition(42, 11, callback); },
    six: function(callback){ strictAddition(23, -17, callback); },
},
function(err, results) {
  if(err) {
    console.log(err);
    return;
  }
  console.log(results); // {twelve: 12, fiftythree: 53, six: 6}
});

try...catch敘述

try...catch敘述無法在異步(asynchronous)的API中解析出錯誤,在Node風格的callback中不能使用。這是初學者很容易犯的毛病,以下是錯誤的示範:

// THIS WILL NOT WORK:
var fs = require('fs');

try {
  fs.readFile('/some/file/that/does-not-exist', function(err, data) {
    // mistaken assumption: throwing here...
    if (err) {
      throw err;
    }
  });
} catch(err) {
  // ... will be caught here -- this is incorrect!
  console.log(err); // Error: ENOENT
}

Node Style Guide(callback的正確用法指引)

出自Node.js Style Guide,第1點上面說過了,第2點比較重要。

1. Always check for errors in callbacks(總是在callbacks中檢查錯誤)

最前面有說明了 - "錯誤優先"處理

//bad
database.get('pokemons', function (err, pokemons) {
  console.log(pokemons);
});

//good
database.get('drabonballs', function (err, drabonballs) {
  if (err) {
    // handle the error somehow, maybe return with a callback
    return console.log(err);
  }
  console.log(drabonballs);
});

2. Return on callbacks(在callbacks時回傳)

意思是對早期(或程式區塊中間)呼叫到callback s的情況,使用return callback()的語法,它的範例給得不是很好,你可能不知道為什麼要在中間錯誤發生時用return console.log(err)

在callback()的下一行加個return;也是可以的作法。記得if敘述中要有花括號。

下面是原作者的範例:

//bad
database.get('drabonballs', function (err, drabonballs) {
  if (err) {
    // if not return here
    console.log(err);
  }
  // this line will be executed as well
  console.log(drabonballs);
});

//good
database.get('drabonballs', function (err, drabonballs) {
  if (err) {
    // handle the error somehow, maybe return with a callback
    return console.log(err);
  }
  console.log(drabonballs);
});

錯誤的示範:


// WRONG Example
function readJSON(filePath, callback) {  
  fs.readFile(filePath, function(err, data) {

    if (err) {
      // error-first callbacks
      callback(err);
    }

    // no error, pass a null and the JSON
    callback(null, JSON.parse(data));
  });
}

正確的作法在下面,因為上面的程式碼,當錯誤發生時(err有值,進入到if程式區塊中執行callback(err)),不會正確中斷程式運作,還會繼續往下執行到callback(null, JSON.parse(data));,有可能會有不預期的錯誤發生:

// this example is **STILL BROKEN**, we are fixing it!
function readJSON(filePath, callback) {  
  fs.readFile(filePath, function(err, data) {
    if (err) {
      return callback(err);
    }

    return callback(null, JSON.parse(data));
  });
}

3. Use descriptive arguments in your callback when it is an "interface" for others. It makes your code readable.

我想原作者想表達的是,如果callback的參數可以敘述得更清楚就敘述清楚,讓程式碼可讀性更高之類的。

// bad
function getAnimals(done) {
  Animal.get(done);
}

// good
function getAnimals(done) {
  Animal.get(function (err, animals) {
    if(err) {
      return done(err);
    }

    return done(null, {
      dogs: animals.dogs,
      cats: animals.cats
    })
  });
}

參考資料