在使用 Node.js 开发时,我们经常会写像这样的代码:

var readFile = require('fs').readFile;

...
readFile('a.js', {encoding:'utf8'}, function (err, dataA) {
  if (err) {
    return callback(err);
  }
  console.log(dataA);
  readFile('b.js', {encoding:'utf8'}, function (err, dataB) {
    if (err) {
      return callback(err);
    }
    console.log(dataB);
    readFile('c.js', {encoding:'utf8'}, function (err, dataC) {
      if (err) {
        return callback(err);
      }
      console.log(dataC);
      ...
    });
  });
});
...

每一个函数执行完毕都会检查是否有错误,有错误则 callback 返回,没有错误则继续往下执行。使用 async 模块后:

var async = require('async');
var readFile = require('fs').readFile;

...
async.series([
  function (done) {
    readFile('a.js', {encoding:'utf8'}, function (err, dataA) {
      console.log(dataA);
      done(err);
    });
  },
  function (done) {
    readFile('b.js', {encoding:'utf8'}, function (err, dataB) {
      console.log(dataB);
      done(err);
    });
  },
  function (done) {
    readFile('c.js', {encoding:'utf8'}, function (err, dataC) {
      console.log(dataC);
      done(err);
    });
  }
], callback);
...

使用 Promise(以 fs-promise 模块为例):

var readFile = require('fs-promise').readFile;

readFile('a.js', {encoding:'utf8'})
  .then(function (dataA) {
    console.log(dataA);
    return readFile('b.js', {encoding:'utf8'})
  })
  .then(function (dataB) {
    console.log(dataB);
    return readFile('c.js', {encoding:'utf8'})
  })
  .then(function (dataC) {
    console.log(dataC);
  })
  .catch(function (err) {
    console.log(err);
  });

使用 co 模块(以 co-fs 模块为例):

var readFile = require('co-fs').readFile;
var co = require('co');

co(function* () {
  var dataA = yield readFile('a.js', {encoding:'utf8'});
  console.log(dataA);
  var dataB = yield readFile('b.js', {encoding:'utf8'});
  console.log(dataB);
  var dataC = yield readFile('c.js', {encoding:'utf8'});
  console.log(dataC);
}).catch(function (err) {
  console.log(err);
});

可以看出,使用生成器函数+yield 不仅解决了回调嵌套的问题,还使代码变得简洁易读、错误处理变得更为优雅,任意一个文件读取失败抛出的异常都会被 co 的 catch 捕获。

koa 是基于 co 开发的,所以可以在 koa 中使用 co 支持的所有特性。因为每一个中间件都是一个生成器函数,中间件又是通过 next 层层传递的,所以我们通常将错误处理放到第一个中间件,如下代码所示:

var app = koa();

app.use(function* (next) {
  try {
    yield next;//此处next代表下游所有中间件
  } catch(err) {
    console.log(err);
  }
});

app.use(logger());
app.use(bodyparser());
...

这种方式能够捕获大多数的错误,但捕获不了没有使用 yield 的异步函数的错误和事件错误等。koa-errorhandler 是 koa 的一个错误处理中间件,使用如下:

var koa = require('koa');
var errorHandler = require('koa-errorhandler ‘);

var app = koa();
app.use(errorHandler());
app.use(function* () {
  foo();// 将会被 errorHandler 捕获 foo is not defined
});

app.listen(3000, function () {
  console.log('listening on port 3000.');
});

koa-errorhandler 能够根据请求头中 Accept 的不同返回不同类型的错误响应(html/json/text),用户也可自定义错误处理器,详见 koa-errorhandler 的 readme。