Generator

130 阅读8分钟

Generator函数是ES6提供的一种异步编程解决方案

Iterator(遍历器)的概念

  • 遍历器Iterator为各种不同的数据结构提供统一的访问机制,任何数据结构只要不输Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员).
  • Iterator的作用有三个:一是为各种数据结构,提供一个统一的,简便的访问接口;而是使得数据结构的成员能够按某种次序排列,三是es6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费
  • Iterator的遍历过程:
  1. 创建一个指针对象, 指向当前数据结构的起始位置.也就是说, 遍历器对象本质上, 就是一个只针对象
  2. 第一次调用指针对象的next方法, 可以将指针指向数据结构的第一个成员
  3. 第二次调用指针对象的next方法, 指针就指向数据结构的第二个成员.
  4. 不断调用指针对象的next方法, 直到它指向数据结构的结束位置.
  • 每一次调用next方法,都会返回数据结构的当前成员的信息.具体来说,就是返回一个包含value和done两个属性的对象.其中,value属性的当前成员的值,done属性是一个布尔值,表示遍历是否结束.
  • 上面代码输出{ value: 1, done: false } { value: 2, done: false } { value: 3, done: false } { value: undefined, done: true }
  • makeIterator函数是一个遍历器生成函数,返回一个遍历器对象,对数组[1,2,3]执行这个函数,就会返回该数组的遍历器对象(即指针对象)it,指针对象的next方法用来移动指针,开始时,指针指向数组的开始位置,然后,每次调用next方法,指针就会指向数组的下一个成员,第一次调用指向1,第二次调用指向2.next方法返回一个对象,表示当前数据成员的信息,这个对象具有value和done两个属性,value属性返回当前位置的成员,done属性是一个布尔值,表示遍历是否结束,即是否还有必要再一次调用next方法.
let it = makeIterator([1, 2, 3]);
let next1 = it.next();
let next2 = it.next();
let next3 = it.next();
let next4 = it.next();
console.log(next1, next2, next3, next4)
console.log(it)
function makeIterator(array) {
    let nextIndex = 0;
    return {
        next: function () {
            return nextIndex < array.length ? {
                value: array[nextIndex++],
                done: false
            } : {
                value: undefined,
                done: true
            }
        }
    }
}

一.基本理解

  1. Genatator函数有多种理解角度,Genarator函数是一个状态机,封装了多个内部状态
  2. 执行Generator函数会返回一个遍历器对象,也就是说Generator函数除了状态机,还是一个遍历器对象生成函数,返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态
  3. 形式上,Generator函数是一个普通函数,但是有两个特征,一是,function关键字与函数名之间有一个星号,二是,函数体内部使用yield语句,定义不同的内部状态(yield在英语里的意思就是 "产出")
function* helloGenerator() {
    yield '初识';
    yield '二刷';
    return '熟识';
};
let hG = helloGenerator();
let hG1 = hG.next();
let hG2 = hG.next();
let hG3 = hG.next();
let hG4 = hG.next();
console.log(hG1, hG2, hG3, hG4);
// { value: '初识', done: false } { value: '二刷', done: false } { value: '熟识', done: true } { value: undefined, done: true }
  • 上面代码一共调用了四次next方法.
  1. 第一次调用, Generator函数开始执行, 直到遇到第一个yield语句为止.next方法返回一个对象, 它的value属性就是当前yield语句的值 '初识', done属性的值false, 表示遍历还没有结束.
  2. 第二次调用, Generator函数从上次yield语句停下来的地方, 一直执行到下一个yield语句.next方法返回的对象的value属性就是当前yield语句的值 '二刷', done属性false, 表示遍历还没有结束.
  3. 第三次调用, Generator函数从上次yield语句停下来的地方, 一直执行到return语句(如果没有return语句, 就执行到函数结束).next方法返回的对象的value属性, 就是紧跟在return语句后面的表达式的值(如果没有return语句, 则value属性的值为undefined), done属性的值true, 表示遍历已经结束
  4. 第四次调用, 此时Generator函数已经运行完毕, next方法返回对象的value属性为undefined, done属性为true, 以后再调用next方法, 返回的都是这个值
  • 调用Generator函数, 返回一个遍历器对象, 代表Generator函数的内部指针, 以后, 每次调用遍历器对象的next方法, 就会返回一个有着value和done两个属性的对象.value属性表示当前的内部状态的值, 是yield语句后面那个表达式的值;
  • done属性是一个布尔值, 表示是否遍历结束.

二.yield语句

  • 由于Generator函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数.yield语句就是暂停标志.
  • 遍历器对象的next方法的运行逻辑如下
  1. 遇到yield语句,就暂停执行后面的操作,并将紧跟在yield后面的表达式的值,作为返回的对象的value属性值.
  2. 下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句.
  3. 如果没有遇到新的yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值
  4. 如果该函数没有return语句,则返回的对象的value属性值为undefined
  • 需要注意的是,yield语句后面的表达式,只有当调用next方法,内部指针指向该语句时才会执行,因此等于为js提供了手动的 '惰性求值'的语法功能.
  • yield语句与return语句既有相似之处,也有区别,相似之处在于,都能返回紧跟在语句后面的那个表达式的值.区别在于每次遇到yield,函数暂停执行,下一次再从改位置继续执行,而return语句不具备位置记忆的功能.一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次或者说多个yield语句.正常函数只能返回一个值,因此只能执行一次return,Generator函数可以返回一系列的值,因此可以有任意多个yidld.
  • Generator函数可以不用yield语句,这时就变成了一个单纯的暂缓执行函数.

三.next方法参数

// yield本身没有返回值,或者说总是返回undefined.next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值.
function* f() {
    for (var i = 0; true; i++) {
        console.log(`i ${i}`)
        let reset = yield i;
        if (reset) {
            i = -1;
        }
        console.log(`reset ${reset}`)
    }
}
let g = f();
let g1 = g.next();
let g2 = g.next();
let g3 = g.next(true);
console.log(g1, g2, g3);
// 输出
// i 0
// reset undefined
// i 1
// reset true
// i 0
// { value: 0, done: false } { value: 1, done: false } { value: 0, done: false }
  • 上面代码先定义了一个可以无限运行的Generator函数f,如果next方法没有参数,每次运行到yield语句,变量reset的值总是undefined,当next方法带一个参数true时,变量reset就被重置为这个参数(即true),因此i会等于-1,下一轮循环就会从-1开始递增.
  • 这个功能有很重要的语法意义,Generator函数从暂停状态到恢复运行,它的上下文状态(context)是不变的.通过next方法的参数,就有办法在Generator函数开始运行之后,继续向函数体内部注入值.也就是说,可以在Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为.
function* foo(x) {
    let y = 2 * (yield(x + 1));
    let z = yield(y / 3);
    console.log(x, y, z);
    return (x + y + z);
}
let f1 = foo(2);
let n1 = f1.next(); // false 3  x值2
let n2 = f1.next(12); // false 8 y值24
let n3 = f1.next(13); // true 39  z值13
console.log(n1, n2, n3);

四.for...of循环

function* fooo() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
    return 6;
}
for (let v of fooo()) {
    console.log(`v ${v}`);
}
// 输出
// v 1
// v 2
// v 3
// v 4
// v 5
  • 上面代码使用for...of循环,依次显示5个yield语句的值,这里需要注意,一旦next方法的返回对象的done属性为true为true,for...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环之中.

五.Generator.prototype.return()

  • Generator函数返回的遍历器对象,还有一个return方法,可以返回给定的值并且终结遍历Generator函数
function* ret() {
    yield 1;
    yield 2;
    yield 3;
}
let re = ret();
let re1 = re.next();
let re2 = re.return(5);
let re3 = re.next();
console.log(re1, re2, re3);
// 输出{ value: 1, done: false } { value: 5, done: true } { value: undefined, done: true }
// 上面代码中,遍历器对象re调用return方法后传入5并终结遍历器Generator函数.
// 如果return方法调用时,不提供参数,则返回值的value属性为undefined

六.Generator函数内部有try...finally

  • 如果Generator函数内部有try...finally代码块,那么return方法会推迟到finally执行完在执行
function* ret() {
    yield 1;
    try {
        yield 2;
        yield 3;
    } finally {
        yield 4;
        yield 5;
    }
    yield 6;

}
let re = ret();
let re1 = re.next();
let re2 = re.next();
let re3 = re.return(15);
let re4 = re.next();
let re5 = re.next();
console.log(re1, re2, re3, re4, re5);
//输出{ value: 1, done: false } { value: 2, done: false } { value: 4, done: false } { value: 5, done: false } { value: 15, done: true }
//上面代码中,调用return方法后,就开始执行finally代码块,然后等到finally代码块执行完,在执行return方法