# Generator生成器对象

MDN:生成器对象是由一个 generator function 返回的,并且它符合可迭代协议迭代器协议。generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次

# 【语法】

注意yield关键字必须放在带*指针的函数里面用

next()方法会执行generator的代码,然后,每次遇到yield x;就返回一个对象{value: x, done: true/false},然后“暂停”。返回的value就是yield的返回值,done表示这个generator是否已经执行结束了。如果donetrue,则value就是return的返回值。(遇到yield就移动指针并标记done:false。遇到return不再迭代就立即标记done:ture并返回,所以return不属于迭代范围内,因为done:true

当执行到donetrue时,这个generator对象就已经全部执行完毕,不要再继续调用next()了。

//例子:
//一遇到yeild然后执行yeild后面的值,执行完毕就暂停
function* read(){
  let a  = yield 'hello';
  console.log(a);
  let b  = yield 'world';
  console.log(b);
}
let it = read()
console.log(it.next())//{ value: 'hello', done: false } undefined
console.log(it.next())//{ value: 'world', done: false } undefined
console.log(it.next())//{ value: undefined, done: true }

在这里插入图片描述

在这里插入图片描述

第二个方法是直接用for ... of循环迭代generator对象,这种方式不需要我们自己判断done:(只会执行done:false的情况并返回,return不会执行

function* gen() { 
  yield 1;
  yield 2;
  yield 3;
  return 4
}
let g = gen(); 
console.log(g);// "Object [Generator] {}"

//需要使用.next()方法或者for(..of..)来迭代执行
console.log(g.next());//{ value: 1, done: false }
console.log(g.next());//{ value: 2, done: false }
console.log(g.next());//{ value: 3, done: false }
console.log(g.next());//{ value: 4, done: true }
console.log(g.next());//{ value: undefined, done: true }

使用for...of...迭代

function* gen() { 
  yield 1;
  yield 2;
  yield 3;
  return 4
}
let g = gen(); 
//第二个方法是直接用for ... of循环迭代generator对象,
//这种方式不需要我们自己判断done:
for(let v of g){
  console.log(v)//一次打印1,2,3
}
//你会发现并不会打印return后的4,因为这样迭代只会执行done:false的情况

我发现在**Generator生成器函数只能被迭代一次**,因为上面两种方法同时使用的话只会执行最先使用的那种迭代方式:

function* gen() { 
  yield 1;
  yield 2;
  yield 3;
  return 4
}
let g = gen(); 
for(let v of g){
  console.log(v)
}
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());
//1
//2
//3
//{ value: undefined, done: true }
//{ value: undefined, done: true }
//{ value: undefined, done: true }
//{ value: undefined, done: true }
//{ value: undefined, done: true }
//使用for..of..迭代之后,再去使用.next()也被认为是迭代完成,不能重新迭代出结果
function* gen() { 
  yield 1;
  yield 2;
  yield 3;
  return 4
}
let g = gen(); 
console.log(g.next());
console.log(g.next());
console.log(g.next());console.log(g.next());
console.log(g.next());
for(let v of g){
  console.log(v)
}
//{ value: 1, done: false }
//{ value: 2, done: false }
//{ value: 3, done: false }
//{ value: 4, done: true }
//{ value: undefined, done: true }
//并不会再去迭代

在generator生成器函数中使用yield* IteratorType可以进行再次迭代

function* generator() {
    yield* 'HEY'
}
for (let i of generator()) {//执行generator函数返回一个迭代器
    console.log(i)
}
//H
//E
//Y

function* generator(iteratorType) {
    yield* iteratorType
}
for (let i of generator([1,2])) {//传入数组Array类型
    console.log(i)
}
//1
//2
for (let i of generator(new Set([3,4]))) {//传入集合Set类型
    console.log(i)
}
//3
//4

# 【总结】

  • 这个*就是一个指针拿来做标记,首先是指向的函数本身并标记此函数是生成器函数然后可以使用yield关键字,可以有.next()或者for..of进行迭代操作。
  • 对于.next()或者for..of两种方式的迭代操作而言,其实原理都是一样的。迭代执行就是移动*指针的过程,执行一步,就将指针移到yield关键字上并且标记上done:false(意思是迭代器还没有工作结束完成),①当以后不再有yield关键字就为下次跌代标记上done:true②当遇到return关键字立即标记上done:true跳出跌代。

# 【方法】

# 【示例】

# 一个无限迭代器

function* idMaker(){
    let index = 0;
    while(true)
        yield index++;
}

let gen = idMaker(); // "Generator { }"

console.log(gen.next().value); 
// 0
console.log(gen.next().value); 
// 1
console.log(gen.next().value); 
// 2
// ...

#进阶

使用生成器对象实现promise,解决回调地狱的问题,简化代码。

使用同步代码的方式写异步代码(但实际上还是执行的异步操作)

#异步操作

使用场景:dva.js

Generator ES6规范里 可以和promise进行配合使用。

特点:可以暂停函数执行

yield 产出

* generator生成器,生产的是什么呢?=>iterator迭代器

function* generator(){
  yield 1
  yield 2
  yield 3
  return 100
}
let it = generator()
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
//结果:
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 100, done: true }
{ value: undefined, done: true }

在ES6里面有一个 async await 就是 generator yield 的和语法糖。我们就进一步了解生成器Generator和Promise的关系。

async await =转换=> generator yield +co(大神tj写的一个nodejs库)

//在./xxx.txt文本文件里面存放着一个'./aaa.txt'文本
//在./aaa.txt文本文件里面存放着一个'哈哈哈'的文本

let fs = require('fs').promises;//他可以直接将fs中的方法变成Promise 10+
function* read() {
  let content = yield fs.readFile('./xxx.txt','utf8');
  let r = yield fs.readFile(content,'utf8');
  return r;
}
let it = read()
let {value,done} = it.next()
Promise.resolve(value).then(data=>{
  let {value,done} = it.next(data)//作为上一个yield的返回值
  Promise.resolve(value).then(data=>{
    let {value,done} = it.next(data)//作为上一个yield的返回值
    console.log(value)//哈哈哈
  })
})

上面的代码虽然是通过使用generator和yield实现了我们想要的功能,但是需要重复的调用差不多的代码。一般情况下我们为了简化重复的代码,都可以使用循环或者递归的方式,但是这里是异步的情况,循环不支持异步。所以就选择递归来实现。(也就是tj大神写的co库)

//优化代码,co的思路
let fs = require('fs').promises;//他可以直接将fs中的方法变成Promise 10+
function* read() {
  let content = yield fs.readFile('./xxx.txt','utf8');
  let r = yield fs.readFile(content,'utf8');
  return r;
}

function co(it) {
  return  new Promise((resolve,reject)=>{
    function exec(data){
      let {value,done} = it.next(data)
      if(!done){//如果done一直是flase
        Promise.resolve(value).then(res=>{//yield的后面可能是普通值,需要强转
          exec(res)//递归
        },reject)//出错就跳出
      }else{
        resolve(data)//停止执行,返回的promse的对象调用.then()获取值
      }
    }
    exec()//第一次执行
  })
}

co(read()).then(res=>{
  console.log(res)//哈哈哈
})

#迭代器接口

拓展:给object对象包装迭代器iterator接口

1】使用for of 循环必须要有iterator迭代器接口

比如Map、Set、Array数组、String、generator函数、arrayLike类似数组的对象(但是必须要添加迭代的功能)、TypedArray等

非常注意:object对象没有迭代器接口,不能直接使用for of循环

var student={
  name:'wujunchuan',
  age:22,
  locate:{
    country:'china',
    city:'xiamen',
    school:'XMUT'
  }
}
for(let key of Object.keys(student)){
  //使用Object.keys()方法获取对象key的数组
  console.log(key+": "+student[key]);
}

如果想要为对象object添加一个迭代器接口我们需要使用[Symbol.iterator]

ES5:(其实上面提到的数组、Map、Set等都是这样使用[Symbol.iterator]包装的迭代器)

//如为对象添加Iterator 接口;
let obj = {
    name: "XX",
    age: 20,
    job: 'teacher',
    [Symbol.iterator]() {
        const self = this;
        const keys = Object.keys(self);//是一个存键的数组
        let index = 0;//使用计数器
        return {
            next() {
                if (index < keys.length) {
                    return {
                        value: self[keys[index++]],
                        done: false
                    };
                } else {
                    return { value: undefined, done: true };
                }
            }
        };
    }
};
 
for(let item of obj) {
    console.log(item); //XX  20  teacher
}

ES6:使用Generator包装一个迭代器

let obj = {
    name: "XX",
    age: 20,
    job: 'teacher',
    * [Symbol.iterator] () {
        const self = this;
        const keys = Object.keys(self);
        for (let index = 0;index < keys.length; index++) {
            yield self[keys[index]];
        } 
    }
};

2】只要是部署了 Iterator 迭代器接口的数据结构,Array.from都能将其转为数组。类似数组的对象也可以被Array.from直接转换成一个数组

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
//Array.from()会为这个类似数组的对象添加迭代功能

let arr3=[...arrayLike]//TypeError: arrayLike is not iterable因为对象没有迭代器接口,转出数组的话需要添加一个迭代器接口才行
let obj4={...arrayLike}//{ '0': 'a', '1': 'b', '2': 'c', length: 3 }


//所以我们就必须给这个arrayLike对象添加一个迭代器接口,
//才能使用[...arrayLike]来转换成真正的数组
let arrayLike = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3,
  [Symbol.iterator](){//Symbol元编程,就是可以更改js的行为(这里就是更改了js对象的功能)
    //迭代器默认是一个对象,具备 next方法 和 调用过后返回的 value和done 的属性
    const self = this
    const keys = Object.keys(self);//是一个存键的数组
    let index = 0
    return {
      next() {
        return {
          value:index < keys.length ? self[keys[index++]] : undefined,
          done:index < keys.length ? false : true
          };
      }
    }
  }
}
// 对象中[Symbol.iterator]()方法必须返回一个对象,而且这个对象中必须要有next()这个方法,这个next()方法需要返回一个对象{value:xx,done:xx}

//迭代器是使用:
let x=arrayLike[Symbol.iterator]()
console.log(x.next());//{ value: 'a', done: false }
console.log(x.next());//{ value: 'b', done: false }
console.log(x.next());//{ value: 'c', done: false }
console.log(x.next());//{ value: 3, done: true }
//下面的扩展运算符相当于使用了for..of,只会执行返回done:false
console.log([...arrayLike]);//[ 'a', 'b', 'c' ]

# 【异步生成器】

Async Generator Functions in JavaScript

总结:记住使用异步迭代器调用next函数之后获得的结果是以Promise的形式进行返回

异步生成器函数是特别的,因为你可以在函数中同时使用await 和 yield, 异步生成器函数不同于异步函数和生成器函数,因为它不会返回promise或者iterator,而是返回异步迭代器,你可以把异步迭代器作为一个**next()函数总是返回一个promise的迭代器**。

# 例子:

异步迭代器函数行为与迭代器函数相似,迭代器函数返回一个有next函数的对象,调用next()将执行generator函数,直到下一个yield。不同的是,异步迭代器的next()返回一个promise

async function* run() {
  await new Promise(resolve => setTimeout(resolve, 100));
  yield 'Hello';
  console.log('World');
}

// `run()` returns an async iterator.
// 执行异步生成器生成异步迭代器
const asyncIterator = run();

// The function doesn't start running until you call `next()`
// 异步迭代器调用next函数,得到第一个yield后面跟的结果,并以Promise形式返回。
asyncIterator.next().
  then(obj => console.log(obj.value)). // Prints "Hello"
  then(() => asyncIterator.next());  // Prints "World"

最干净的方法是使用for/await/of循环整个异步迭代器函数:

async function* run() {
  await new Promise(resolve => setTimeout(resolve, 100));
  yield 'Hello';
  console.log('World');
}

const asyncIterator = run();//生成异步迭代器

// Prints "Hello\nWorld"
//使用async、await、for:循环一步等待一步
(async () => {
  for await (const val of asyncIterator) {
    console.log(val); // Prints "Hello"(val是yield后的结果以Promise形式返回,所以需要await去获取它的值)
  }
})();

#

Last Updated: 8/1/2021, 1:57:14 PM