koa中间件执行顺序之iterator,yield与generator

主要是先理解iterable/iterator/yield/yield\*/generator

koa不同与express的一点就是中间件的使用。express的中间件是js的回调,koa的中间件是基于es6iterator/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 *


yieldSymbol.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方法时候,yieldreturn都可以返回一个包含value的对象,区别是yeilddonefalse,而returndonetrue,并且执行到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方法执行到才行。

上面的例子中,objg3的对象实例,而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):

results matching ""

    No results matching ""