KOA框架为何这么屌

最近逢人就说KOA用着好爽啊,但每次都被泼冷水

首先是看不起Nodejs派:“能跟我大structs比?”。我不得不低头,这真比不起,express就因为太小没法和那些”大框架”比,KOA比express还小几倍,核心部分就100来行,说出来岂不是让java程序员笑死,自讨没趣

其次是express派:“express就够屌了,我看KOA也就和express一样嘛”,我马上回到,不是有generator么,多方便。express派说:那不过是用了点ES6的特性,KOA本身没啥进步的。我一时语塞

不管别人怎么看,KOA确实用着爽,忍不住扯KOA的两点好

1. yield数组

yield处理逐个执行的异步函数上次已经说过了

yield后面还可以跟数组,如下

var fs = require('fs')
var app = require('koa')()
var readFile = function(dir) {
  return function(fn) {
    fs.readFile(dir, fn)
  }
}
app.use(function* () {
  var arr = yield ['1.txt', '2.txt', '3.txt'].map(function(path) {
    return readFile(path)
  })
  this.body = arr.join(',')
})
app.listen(8000)

其实这不过是上上篇中提到的不太难的多个平行异步函数获取总的回调函数的yield写法,不过也已经简洁到令人赞叹的地步了

试想下如果我们同时要获取数据库中多个内容,代码也会非常简洁

可问题来了,如果这其中有异步函数出错怎么办?

2. KOA中的错误处理

KOA中的错误处理才是真正让人叹为观止的地方,牛逼的同学可以看看generator’s throw feature

直接上写法

var fs = require('fs')
var app = require('koa')()
var readFile = function(dir) {
  return function(fn) {
    fs.readFile(dir, fn)
  }
}
app.use(function* (next) {
  try {
    yield next
  } catch(e) {
    return this.body = e.message || "I'm dead"
  }
})
app.use(function* () {
  var arr = yield ['4.txt', '2.txt', '3.txt'].map(function(path) {
    // 4.txt不存在
    return readFile(path)
  })
  this.body = arr.join(',')
})
app.listen(8000)

可以看到访问网页的返回结果ENOENT, open '4.txt'

我们在app的匹配栈上直接给yield next套上try catch

当然KOA本身也是会处理这些错误的,比如还提供了app.onerror这些东西,甚至自动做了404或者500的判断,源码如下

// delegate
this.app.emit('error', err, this);

// force text/plain
this.type = 'text';

if ('ENOENT' == err.code) err.status = 404;

// default to 500
err.status = err.status || 500;

// respond
var code = http.STATUS_CODES[err.status];
var msg = err.expose ? err.message : code;
this.status = err.status;
this.res.end(msg);

但我们往往需要自己处理错误: 处理数据库错误,处理文件读取错误,读取解析错误… 如果用自带的错误处理肯定会有人不解为什么在app.onerror中就不能用酷炫的this.body=了呢?也会疑惑ctx.res用了之后怎么会二次出错

因此我觉得在最前面加上next函数的try catch包裹非常有必要,这样我们可以统一的处理这些错误,任何yield错误都会被抓到,我们只需分析e.message以及e.code即可,我们甚至可以玩出很多花样来,利用this.throw()new Error()来定义自己的错误,简化错误管理,还能对错误进行归类。

代码要写在try catch外面

虽然用try catch 捕捉next错误很爽,但我们知道不管是try块中的代码还是catch块中的代码,都是无法让V8引擎进行任何优化的(具体原因忘了),不能优化的函数比优化的函数会慢上好几倍,也就是说try和catch中的代码比外面的慢很多倍。但是如果是调用try catch块外面的函数就不会有这个问题了。因此那段头部代码应该这么写

function errHandler(e) {
  ...
}
app.use(function* (next) {
  try {
    yield next
  } catch(e) {
    // 所有处理错误的代码都放在外面
    errHandler(e)
  }
})

不要小看这个,不信的话可以做这个试验

console.time(1)
try {
  throw new Error()
} catch(e) {
  for (var i = 0; i < 1000000000; i++) {
    var p = i % 2
  }
}
console.timeEnd(1)

// 取出来放在外面
console.time(2)
try {
  throw new Error()
} catch(e) {
  run()
}
console.timeEnd(2)
function run() {
  for (var i = 0; i < 1000000000; i++) {
    var p = i % 2
  }
}

结果是

1: 8352ms
2: 1294ms

差距还是相当大的

有了错误管理yield这俩大杀器,Nodejs中被人诟病的缺陷就足以解决一大半!