JS中Generator函数

202 阅读3分钟

一、 Generator的定义

1.特点

  • Promise 是为了解决回调地狱(Callback Hell)的问题而出现的,而生成器函数(Generator Functions)则是为了解决异步问题而出现的。
  • 普通函数在调用时会立即执行,并一直执行到函数体中的所有代码执行完毕或遇到 return 语句返回结果。这种执行方式称为"一次性执行"。
  • Generator函数在调用时不会立即执行函数体中的所有代码,而是返回一个迭代器(Iterator)。通过迭代器可以控制生成器函数的执行,可以暂停执行、恢复执行和产生多个值。

2.基本用法

//定义生成器函数
function* gen(num) {
    let r1 = yield 1
    console.log('r1', r1);

    let r2 = yield 2
    console.log('r2', r2);

    let r3 = yield 3
    console.log('r3', r3);

    return 4;

}
// 定义迭代器对象
const iterator = gen()

/*
    遇到 yield 1 停止执行
    输出结果:
        {value:1, done:false}

    value   表示yield后面的值或return的值
    done    表示生成器是否已完成 
        还没有走到函数的return语句或最后一次.next时,done总是false
*/
console.log(iterator.next())

/*
    输出结果:
        r1 undefined
        {value:2, done:false}
    yield的返回值并不会返回给等号左边的变量, 因为generator 函数在遇到 yield 时就已经暂停执行了,并不会执行到赋值操作
    如果想赋值可以用 next 方法进行传值
        iterator.next('A')
*/
console.log(iterator.next())
/*
    输出结果:
        r2 A
        {value:3, done:false}
    next先进行赋值操作,将参数赋值给r2
*/
console.log(iterator.next('A'))
/*
    输出结果:
        r3 undefined
        {value:4, done:true}
    如果函数有return值,最后一个next方法,它的value值为return的值;如果没有值为 undefined; done为true。
*/
console.log(iterator.next())

3.Generator函数嵌套使用

function* gen1() {
    yield 1
    yield 2
}

function* gen2() {
    yield 3
    // generator函数的嵌套
    // 这种写法对应 方案1
    // yield gen1()
    yield* gen1()
    yield 4
}

const iterator = gen2()
console.log(iterator.next()); // {value:3,done:false}

// 如果我们想执行到 gen1 中的 yield 值
// console.log(iterator.next()); // {value:generator实例,done:false}
// let itor = iterator.next().value
// console.log(itor.next()); // {value:1,done:false}
// console.log(itor.next()); // {value:2,done:false}

// 方案2
console.log(iterator.next()); // {value:1,done:false}  你需要在yield后面加一个*,让它知道后面是一个generator对象
console.log(iterator.next()); // {value:2,done:false}
console.log(iterator.next()); // {value:4,done:false}
console.log(iterator.next()); // {value:undefined,done:true}

二、引入co库

  • Generator 函数本身并没有提供自动执行的能力,需要通过手动调用 next() 方法来控制函数的执行。
  • co 是一个第三方库,它是基于 Generator 函数的自动执行器。co 库可以自动迭代 Generator 函数的迭代器,并根据 yield 表达式返回的 Promise 对象的状态,自动调用 next() 方法,并将 Promise 的解析值传递给 Generator 函数。它简化了使用 Generator 函数进行异步编程的过程。
const co = require('co');

function* myGenerator() {
  try {
    const result1 = yield Promise.resolve('Result 1');
    console.log(result1);

    const result2 = yield Promise.resolve('Result 2');
    console.log(result2);
  } catch (error) {
    console.error('Error:', error);
  }
}

co(myGenerator())
  .then(() => {
    console.log('Generator function completed');
  })
  .catch(error => {
    console.error('Error:', error);
  });

/*
    Result 1
    Result 2
    Generator function completed
*/

三、async/await原理(手写 Generator自动执行器)

async/await 是基于 Generator 函数和 Promise 的组合使用。下面是使用 Generator 函数来手动实现一个类似于 async/await 的功能。

function runGenerator(generator) {
  const iterator = generator(); // 获取 Generator 函数的迭代器

  function iterate({ value, done }) {
    if (done) {
      return Promise.resolve(value); // 如果迭代器已完成,直接返回结果
    }

    return Promise.resolve(value) // 将当前 yield 表达式的值转为 Promise 对象
      .then((result) => {
        return iterate(iterator.next(result)); // 递归调用 iterate 函数,继续执行下一个 yield 表达式
      })
      .catch((error) => {
        return iterate(iterator.throw(error)); // 如果出现异常,使用迭代器的 throw 方法抛出异常
      });
  }

  return iterate(iterator.next()); // 开始执行第一个 yield 表达式
}

四、生成器的迭代(手写for...of遍历obj对象)

1.生成器的迭代

  • 生成器对象可以通过 for...of 循环进行迭代,或者使用扩展操作符 ... 将生成器转换为数组。
function* myGenerator() {
  yield 'a';
  yield 'b';
  yield 'c';
}

for (const item of myGenerator()) {
  console.log(item); // 'a', 'b', 'c'
}

const arr = [...myGenerator()];
console.log(arr); // ['a', 'b', 'c']

2.手动实现使用 for...of 循环遍历对象

const person = {
    name: 'lzh',
    age: 21
  }
  
  // 方法一
  person[Symbol.iterator] = function* () {
      yield* Object.values(this)
  }
  
  // 方法二
  person[Symbol.iterator] = function* () {
    for (let x in this) {
        yield this[x] 
    }
  }
  
  //方法三
   person[Symbol.iterator] = function () {
    const keys = Object.keys(this);
    let index = 0;
    return {
      next:()=> {
        if (index < keys.length) {
          return {
            value: this[keys[index++]],
            done: false
          }
        } else {
          return {
            value: undefined,
            done: true
          }
        }
      }
    }
  }