koa中间件执行顺序之iterator,yield与generator
主要是先理解iterable/iterator/yield/yield\*/generator
koa
不同与express
的一点就是中间件的使用。express
的中间件是js
的回调,koa
的中间件是基于es6
的iterator/yield
。要弄明白它的工作流程,就得理解iterator/iterable/yeild/yeild\*/generator
。
iterator与iterable
iterator
是可以理解成遍历器,es6
中一种新的数据访问协议:
- 对象统一的访问方式
- 对象内容的一种排序
- 增加for of
原来数据格式中,有数组、字符串以及新增的map、set
具有可遍历性,具有Symbol.iterator
属性,返回一个可遍历对象。原生object
不具有可遍历对象,但是可以自定义增加。
说一个对象可遍历的,则该对象具有next
方法,运行后返回一个对象:
next : function () {
return {
value : 'smValue',
done : false //表示依然后续有值,若为true时,则表示已经没与值,value为undefined
}
}
所以根据所谓的”鸭子辨型“,我们要让一个对象是可遍历的,也若上布置next
方法,返回规定的对象即可。
next : function () {
var index = 0;
return function (){
if (true) {
return {
value : index,
done : false
}
} else {
return {
value : undefined,
done : true
}
}
}
}
原型链也是可以直接部署Symbol.iterator
,需在原型链上部署next
方法,还需要让Symbol.iterator
返回该对象。
var Obj = function (){}
Obj.prototype[Symbol.iterator] = function (){
return this;
}
Obj.prototype.next = function (){
var index = 0;
return {
function (){
if (true) {
return {
value : index,
done : false
}
} else {
return {
value : index,
done : true
}
}
}
}
}
说 到这儿,iteratable
意思是可遍历的。与iterator
遍历器区别明显。
yield与yield *
yield
是Symbol.iterator
的一种语法糖,返回一个对象。yield *
返回的是一组可遍历的对象。不过用yeild
的方法上需带有星号来标识。
function* g1() {
yield 2
yield 3
}
function* g2() {
yield 1
yield g1()
yield* g1()
yield [4, 5]
yield* [6, 7]
}
const iterator = g2()
//console.log
iterator.next() 1
iterator.next() {} // yield g1() 返回的值,只是返回一个g1的object对象。如要正常访问g1内部的值,得用yeild * 访问,即如下:
iterator.next() 2
iterator.next() 3
iterator.next() [4,5]
iterator.next() 6
iterator.next() 7
iterator.next() undefined
在调用next
方法时候是可以传参的:
function* g3(){
for (var i = 0; i<10000; i++) {
var mk = yield i;
if (mk === true) {
yield 'sss'
}
}
}
var m = g3();
m.next(); // 0
m.next(); //1
m.next(); //2
m.next(true); //sss next的参数是既是上次yield的返回值,如果不传则为undefined,此处如果不传值则mk的值为undefined。因此可以理解为首次不用传值
m.next(); //3
在调用next
方法时候,yield
与return
都可以返回一个包含value
的对象,区别是yeild
的done
为false
,而return
的done
为true
,并且执行到return
,则该函数运行结束。
function* g3(){
for (var i = 0; i<10000; i++) {
var mk = yield i;
if (mk === true) {
return 'sss'
}
}
}
var m = g3();
m.next(); // 0 {value: 0, done: false}
m.next(); //1 {value: 1, done: false}
m.next(); //2 {value: 2, done: false}
m.next(true); //sss {value: "sss", done: true}
m.next(); //3 {value: undefined, done: true}
另外一个特别的地方是异常的处理:try...catch
。在一个包含yeild
的函数中,如果进行了异常捕获,则可以捕获所有调用时候的异常,即所有调用的异常在这个函数中即可捕获。
function* g() {
try {
for (var i = 0; i < 10000; i++) {
yield i;
}
} catch (e) {
console.log('内部捕获', e)
}
}
var i = g();
i.next();
i.throw('错误!') // 内部捕获,错误
需要注意的是,生成器需要执行一次next
才可以进行捕获。一旦捕获错误,则此遍历结束,若仍调用next
方法,只会返回{value:undefined,done:true}
function* g() {
try {
for (var i = 0; i < 10000; i++) {
yield i;
}
} catch (e) {
console.log('内部捕获', e)
}
}
var i = g();
i.throw('错误!') // VM7138:1 Uncaught 错误! 此处为外层的报错
i.next()// 调用异常的触发遍历结束 Object {value: undefined, done: true}
=======
function* g() {
try {
for (var i = 0; i < 10000; i++) {
yield i;
}
} catch (e) {
console.log('内部捕获', e)
}
}
var i = g();
i.next()
i.throw('错误!') //内部错误,错误!
i.next()// {value: undefined, done: true} 内部捕获异常让遍历结束
其实,例子中的i是g的一个实例,异常致使i不能正常遍历,但是不影响其他实例进行遍历
var io = g()
io.next()
Object {value: 0, done: false}
generator
generator
是生成器,用于产生可遍历对象。
function* g3(){yield 3}
上文中的g3
就是一个生成器。函数中的星号可以紧跟function
关键字,也可以紧跟函数名。
虽然gengertaor
可以看成一个类,但是却不能和new
关键字进行一起用。并且无法直接访问函数当前this
(其实是生成器实例的运行域)的属性。至于生成器内部变量则是无法访问的
function* g3(){
this.a = 1;
var d = 4;
yield this.b =2;
yield this.c= 3;
}
var f = g3();
f.a //undefined
f.next();
f.a //undefined
f.d //undefined
但是我们可以把函数的this
绑定到一个对象上,这样就可以访问了。
function* g3(){
this.a = 1;
yield this.b =2;
yield this.c= 3;
}
var obj = {} //待绑定的对象
var f = g3.call(obj);
f.a //undefined
f.next();
obj.a //1 可以看出,若想访问this的属性,需要next方法执行到才行。
上面的例子中,obj
是g3
的对象实例,而f
是生成器实例。我们可以利用prototype
本质是一个对象来实现实例对生成器实例的统一
function* g3(){
this.a = 1;
yield this.b =2;
yield this.c= 3;
}
var f = g3.call(g3.prototype);
f.a //undefined
f.next();
obj.a //1
还稍微做下该变,使用new
function* g3(){
this.a = 1;
yield this.b =2;
yield this.c= 3;
}
function F(){
return g3.call(g3.prototype);
}
var f = new F();
f.a //undefined
f.next();
f.a //1
koa的执行顺序
关于koa
的执行顺序,先看例子:
var koa = require('koa');
var app = koa();
app.use(function* (next) {
console.log(11111)
yield next;
})
app.use(function* (next) {
console.log(444)
yield next;
console.log(5555)
yield next;
console.log(6666)
})
app.use(function* (next) {
console.log(7777)
yield next;
console.log(8888)
yield next;
})
app.use(function *(next){
console.log(99999);
this.body = 'Hello World';
});
app.listen(3000);
console.log('koa server is running on port 3000')
访问3000
端口后,打印如下:
11111
444
7777
99999
8888
5555
6666
若做测试的时候,谷歌浏览器访问后打印两边,则是发了两个请求:一个是/
,一个是/favicon
.
由此 可以推测koa
内部的中间件(yield)
访问顺序是访问遇到yield
则跳过/暂存方法内以后得内容访问下个中间件,直到yield
后没有中间件了,则倒序访问之前的中间件。当中间件有多个yield
时,正序访问中间时候则运行到每个中间件的第一个yield
即继续下个中间件,当倒序访问中间件时则会把改中间件第一个yield
后的所有代码运行完。如上代码正序访问时候 ,输出:
11111
444
7777
99999
然后倒序访问到8888
的中间价时候,输出8888
,访问到4444
中间件时,则连续运行两次输出:
5555
6666
另外附一张图(来源知乎用户https://www.zhihu.com/question/30258965):