blog icon indicating copy to clipboard operation
blog copied to clipboard

KOA框架为何这么屌

Open chunpu opened this issue 10 years ago • 0 comments

最近逢人就说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)

其实这不过是[上上篇]({{ page.previous.previous.url }})中提到的不太难的多个平行异步函数获取总的回调函数的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中被人诟病的缺陷就足以解决一大半!

chunpu avatar Jan 10 '15 04:01 chunpu