由 Express 原班人马打造的 koa,致力于成为一个更小、更健壮、更富有表现力的 Web 框架。使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升常用错误处理效率。Koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手。
我觉得其实koa主要就是避免了繁琐的回调函数嵌套,代码看着更和谐。还有就是它的错误处理机制。
其实这几天一直在纠结为什么generator是一种异步方案。觉得真的好奇怪。因为你用generator要一直调用next方法才能继续,我想这就是阻塞的啊,怎么能是异步的呢。后来发现有些书上这样写是错误的。generator不是一种异步方案,它是和其他的一些配合才能实现异步。比如promise。由于yield以前也没用过,yield后面yieldable的内容又是有限的。自然就没有max(我nice的同事)说用的那么顺手。
解决我的疑问
后来我在一篇文章experiments-with-koa-and-javascript-generators找到了答案。
也就是说promise+generator或者generator和其他异步方案一起合作才是正解。直接说generator就是异解决方案是不准确的。
koa的中间机制原理
那么koa 中间件机制实现原理是什么呢?为什么中间我们一定要传入一个generator当作函数呢?一定需要用yield吗?yield的作用是什么呢?next又是什么?yield next为什么可以就顺序的执行下一个中间件?
为什么中间件从上到下执行完后,可以从下到上执行 yield next 后的逻辑?这是我用这个框架的时候的疑问。
先来看一种实现koa中间机制的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30var co = require('co');
class koa {
constructor (){
this.middlewares = [];
}
use(middleware){
this.middlewares.push(middleware);
}
listen(){
this._run();
}
_run(){
var ctx = this;
var middlewares = this.middlewares;
return co(function *(){
var prev = null;
while (i--) {
prev = middlewares[i].call(ctx, prev);
}
//执行第一个中间件
yield prev;
})();
}
}
然后我们以原始跟原始koa项目一样的代码来运行:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17var app = new koa();
app.use(function *(next){
this.body = '1';
yield next;
this.body += '5';
console.log(this.body);
});
app.use(function *(next){
this.body += '2';
yield next;
this.body += '4';
});
app.use(function *(){
this.body += '3';
});
app.listen();
可以很清楚的发现是一样的结果。1,2,3,4,5。koa这种思想中yield相当于一个断点。到这个地方就停了。然后接着执行下一个next。很类似我们学过的递归的思想。它的思想就是把一系列中间件放入一个数组中,然后从第一个开始执行。
koa的错误处理机制
一般我们在js的错误处理机制中有一下几种方法:
- 和其他同步语言类似的 throw / try / catch 方法
- callback(err, data) 回调形式
- 通过 EventEmitter 触发一个 error 事件
koa 通过可以通过它的特性让我们可以使用 catch 来捕获异步代码中的错误。
比如:
1 | const fs = require('fs'); |
koa主要是通过co来实现。co是tj写的用来自动执行generator的小工具。如果我们不利用co,调用generator的时候,需要一直next,co帮助自动执行。
一般我们把中间件可以称作MVC里面的M,也就是逻辑业务处理层。最好的方式,是我们使用中间件来处理koa的错误。
1 | app.use(function* (next) { |
当我们只在try块里面执行yield的时候。我们的错误都放在catch里面,然后再对catch到的内容做处理,就是一种很有效的方法。
当我们触发错误的时候:1
2
3
4
5
6
7
8
9
10
11const router = new (require('koa-router'));
router.get('/some_page', function* () {
// 直接抛出错误,被中间件捕获后当成 500 错误
throw new PageError('发生了一个致命错误');
throw new JsonError('发送了一个致命错误');
// 带 status 的错误,被中间件捕获后特殊处理
this.throw(403, new PageError('没有权限访问'));
this.throw(403, new JsonError('没有权限访问'));
});
这里的JsoneError和PageError是定义的错误构造器。1
2
3
4
5
6
7
8
9
10
11
12
13
14const util = require('util');
exports.JsonError = JsonError;
exports.PageError = PageError;
function JsonError(message) {
Error.call(this, message);
}
util.inherits(JsonError, Error);
function PageError(message) {
Error.call(this, message);
}
util.inherits(PageError, Error);
通过将代码细分,就可以得到更清晰的结果。这一部分学习自:淘宝fedkoa错误处理。
总结
关于co中间的原理,比如返回promise,比如利用到了trunk。这些我还没有完全理解清楚,等理解清楚了再来记录下。推荐几篇文章:
- 阮一峰es6入门:http://es6.ruanyifeng.com/#docs/async#co模块
- promise实现:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
- koa中文文档: https://github.com/guo-yu/koa-guide
- koa异步讲解:http://blog.stevensanderson.com/2013/12/21/experiments-with-koa-and-javascript-generators/
- 淘宝fedkoa错误处理:http://taobaofed.org/blog/2016/03/18/error-handling-in-koa/