ES6-深入理解Generator yield & Koa中间件执行顺序

来源:互联网 发布:数据超市 编辑:程序博客网 时间:2024/05/22 14:14

几个月前写过一篇博客,讲Generator,比较基础。最近总在写ES6,想深入讲讲yield的执行顺序。你可能想问,Generator执行顺序很简单啊,就是调用next()就执行下一个yield后面的代码。很多问题,如果你认为很简单,很可能是你理解不深刻,就像我当初也认为Generator很简单。如果你关心koa中间件的执行顺序也可以接着看看。

yield与yield*

关于这个话题,你只需要知道四点:

  • yield* fn():相当于用fn的内容来替换该位置,不会消耗一次next()调用,fn内的代码会被执行
  • yield* fn:报错。因为fn是一个generator function,而yield*后面应该是一个generator
  • yield fn():yield的结果是一个generator,消耗一次gen的next()调用,且fn内的代码不会被执行
  • yield fn: yield的结果是一个generator function,消耗一次gen的next()调用,且fn内的代码不会被执行
    例子:
class Test{    constructor(){        this.gen = this.f1();        this.gen.next();        this.gen.next();    }    *noop() {}    *f1() {    console.log('1: pre');    yield this.f2();    console.log('1: after');    }    *f2() {    console.log('2: pre');    yield this.f3();      console.log('2: after');    }    *f3() {    console.log('3: pre');      console.log('3: after');    }}let test = new Test();

打印出:

1: pre1: after

没有执行f2,f3代码,因为yield不会继续执行后面的函数,但是yield* 会继续执行后面的函数,知道得出一个结果。把上面代码的yield换成yield*,执行结果:

1: pre2: pre3: pre3: after2: after1: after

如果你知道Koa,你会发现,这就是Koa中间件的执行顺序,但是Koa不是通过yield*实现Generator的自动执行,而是通过co。

Koa中间件执行顺序的原理

下面是一段koa代码,如果你不知道koa也没关系,只要知道koa的所有中间件都是Generator函数:

var koa = require('koa');var app = koa();app.use(function* f1(next) {    console.log('f1: pre next');    yield next;    console.log('f1: post next');});app.use(function* f2(next) {    console.log('  f2: pre next');    yield next;    console.log('  f2: post next');});app.use(function* f3(next) {    console.log('    f3: pre next');    this.body = 'hello world';    console.log('    f3: post next');});app.listen(4000);

你感觉执行顺序是什么?

答案:

f1: pre next  f2: pre next    f3: pre next    f3: post next  f2: post nextf1: post next

执行顺序:
这里写图片描述

并不是执行完f1再执行f2,最后执行f3。
似乎和上面yield*执行顺序有点相似呢。

compose

上述带yield*的函数为了实现这个“回形”执行顺序时候,在定义f1的时候需要yield* f2(),显然Koa不可能在注入每个中间件时候再改变其内部yield的内容,那么我们就需要compose函数了。

function compose(middleware) {    return function*(next) {        var i = middleware.length;        var prev = next || noop();        var curr;        while (i--) {            curr = middleware[i];            prev = curr.call(this, prev);        }        yield* prev;    }}function* noop() {}

调用方式

let gen = compose([f1, f2, f3]); //f1,f2,f3是带yield的Generator Function//gen是一个类似 f1(f2(f3({})))的Generator对象,Generator对象是调用一次Generator Function返回的结果

co

如果你执行gen.next()会返回11: prev,不能调用f2和f3,当然,如果你和上面一样,把yield都改成yield* 是会链式调用f2,f3的。可是koa没有规定必须使用yield*。为了执行gen,koa引入了co——一个Gnerator自动执行函数。

const co = require ('co');co(gen);

f1, f2,f3就可以实现上述“回形”执行了。如果你想深入co原理,请戳co。

Generator的GeneratorStatus

更深入一点,如果有多个yield会怎么样。

        class Test{            constructor(){                this.g = this.compose([this.f1, this.f2, this.f3])();                co(this.g);            }            *noop() {}            *f1(next) {            console.log('11: prev');            yield next;            console.log('11: after');            console.log('12: prev');            yield next;            console.log('12: after');            }            *f2(next) {            console.log('21: prev');            yield next;              console.log('21: after');              console.log('22: prev');            yield next;              console.log('22: after');            }            *f3(next) {            console.log('31: prev');            yield next;              console.log('31: after');              console.log('32: prev');            yield next;              console.log('32: after');            }            compose(middleware) {                let self = this;            return function*(next) {                var i = middleware.length;                var prev = next || self.noop();                var curr;                while (i--) {                    curr = middleware[i];                    prev = curr.call(this, prev);                }                yield* prev;            }            }        }        let test = new Test();

会不会 f1 -> f2 -> f3 -> f2 遇到f2的第二个yield又去执行f3?
答案是不会的。遇到f2第二个yield会跳过,继续执行下面语句,不会再执行一遍f3。原因和简单,Generator对象是有状态的,即GeneratorStatus属性,其值从suspended变为closed后,不会再改变。就是说,Generator对象在一个环境中,只能执行一遍。上述代码执行结果:

11: prev21: prev31: prev31: after32: prev32: after21: after22: prev22: after11: after12: prev12: after

这里写图片描述
当一个Generator函数没有未执行的yield,变为普通函数,GeneratorStatus的值从suspended变为closed。

0 0
原创粉丝点击