内容来自 > ES6+ Generator

生成器对象和生成器函数

  1. 生成器对象 是遵守迭代协议和迭代器协议实现的 Iterable 接口,可以理解 生成器对象 其实也是一个迭代器;

  2. 生成器函数 是由 function * 来定义的,并且返回结果是一个 Generator 对象;

  3. 生成器可以使用 yield 关键字来暂停执行的生成器函数;

    function* generator() {
      yield 'a';
      yield 'b';
    }
    
    var gen = generator();	
    console.log(gen); // Object [Generator] {}
    

Generator.prototype.next()

  1. 生成器的 next() 方法和迭代器返回的结果是一样的,返回了一个包含属性 donevalue 的对象,该方法也可以通过接受一个参数用以向生成器传值;

  2. 使用 yield 返回的值会被迭代器的 next() 方法捕获;

    function* generator() {
      yield 'a';
      yield 'b';
    }
    
    // 生成器函数在执行后会返回一个生成器对象,这个生成器对象满足迭代协议和迭代器协议
    var gen = generator();
    
    // 手动调用它的 next () 方法去获取每一步的返回值
    gen.next();	// {value: 'a', done: false}
    gen.next();	// {value: 'b', done: false}
    gen.next();	// {value: undefined, done: true}
    

Generator.prototype.return()

  1. return() 方法返回给定的值并结束生成器;

    function* generator() {
      yield 'a';
      yield 'b';
    }
    var gen = generator();
    
    gen.next();           // { value: 'a', done: false }
    gen.return("hello");  // { value: "hello", done: true }
    gen.next();           // { value: undefined, done: true }
    
  2. 如果对已经完成状态的生成器调用 return(value) 则生成器会一直保持在完成状态,如果传入参数,value 会设置成传入的参数,done 的值不变;

    function* generator() {
      yield 'a';
      yield 'b';
    }
    var gen = generator();
    
    gen.next();     // { value: 'a', done: false }
    gen.next();     // { value: 'b', done: false }
    gen.next();     // { value: undefined, done: true }
    gen.return();   // { value: undefined, done: true }
    gen.return(1);  // { value: 1, done: true }
    

Generator.prototype.throw()

  1. throw() 方法用来向生成器抛出异常,并恢复生成器的执行,返回带有 donevalue 两个属性的对象;

    function* generator() {
      while(true) {
        try {
          yield 'hello'
        } catch(e) {
          console.log("Error caught!");
        }
      }
    }
    var gen = generator();
    gen.next(); // { value: "hello", done: false }
    gen.throw(new Error("error")); // "Error caught!",{value: 'hello', done: false}
    

Generator 案例

类数组转化

JavaScript
JavaScript
const likeArr = { 0: 1, 1: 2, length: 2 };

// 方式一:实现迭代器(手动实现 next 函数)
likeArr[Symbol.iterator] = function () {
  let index = 0;
  return {
    next: () => ({ value: this[index], done: index++ === this.length });
  }
}

console.log([...likeArr]); // [1, 2]
const likeArr = { 0: 1, 1: 2, length: 2 };

// 方式二:生成器实现(不需要手动实现 next () 方法,生成器函数更加简单方便)
likeArr[Symbol.iterator] = function* () {
  let index = 0;
  while (index !== this.length) {
    yield this[index++];
  }
}

console.log([...likeArr]); // [1, 2]

单步获取质数

题目:实现一个函数,每次调用返回下一个质数,要求不使用全局变量,且函数本身不接受任何参数

JavaScript
JavaScript
JavaScript
// 方式一:闭包方法来解决
function primeHandler() {
  let prime = 1;

  return () => {
    while (true) {
      if (isPrime(++prime)) return prime;
    }
  }
}

function isPrime(num) {
  for (let i = 2; i <= Math.sqrt(num); i++) {
    if (num % i === 0) return false;
  }
  return true;
}

const getPrime = primeHandler();
console.log(getPrime());   // 2
console.log(getPrime());   // 3
console.log(getPrime());   // 5
// 方式二:迭代器方式实现
var prime = {
  [Symbol.iterator]() {
    let prime = 1;
    return {
      next() {
        while (true) {
          if (isPrime(++prime)) return prime;
        }
      }
    };
  }
};

function isPrime(num) {
  for (let i = 2; i <= Math.sqrt(num); i++) {
    if (num % i === 0) return false;
  }
  return true;
}

var getPrime = prime[Symbol.iterator]().next;
console.log(getPrime());   // 2
console.log(getPrime());   // 3
console.log(getPrime());   // 5
// 方式三:生成器函数实现
function* primeGenerator() {
  let prime = 1;
  while (true) {
    if (isPrime(++prime)) yield prime;
  }
}

function isPrime(num) {
  for (let i = 2; i <= Math.sqrt(num); i++) {
    if (num % i === 0) return false;
  }
  return true;
}

var getPrime = primeGenerator();
console.log(getPrime.next().value);   // 2
console.log(getPrime.next().value);   // 3
console.log(getPrime.next().value);   // 5

Generator 函数应用

  1. Generator 函数在异步中的应用,解决了某些场景下还会产生回调地狱的问题,通过封装 co 方法让代码写起来像是同步一样;

  2. Generator 函数还不是解决异步的终极方案,后面会学习 async 函数 (async + await 相当于 generator + co),看它是怎么来解决异步的;

回调地狱问题

  1. 如果想要获取第二个文件中的内容,但是文件名未知,需要读取 name.txt 之后才知道这个文件名字;

  2. 如果使用了两层 promsie 嵌套,则代码的书写方式并不友好,若需求是 n 个文件岂不是需要嵌套 n 层?

Generator 初步解决

// 第一个文件:name.txt 的内容 age.txt
// 第二个文件:age.txt  的内容 10
const fs = require('fs').promises;

// 代码编写更像是同步的,更友好的编码方式 (执行还是异步的)
function* read() {
  try {
    let name = yield fs.readFile('name.txt', 'utf8');
    let age = yield fs.readFile(name, 'utf8');
    return age;
  } catch (error) {
    console.log(error);
  }
}

let it = read(); // 获取生成器对象
let { value, done } = it.next(); // value 是个 promise

// 从下面的代码中看出还是有嵌套,细心观察发现每个回调的逻辑基本都是一样的,可以使用 co 库来解决这个问题
value.then(data => { // 每个回调的逻辑基本都是一样
  let { value, done } = it.next(data);
  console.log(value, done);

  value.then(data => { // 每个回调的逻辑基本都是一样
    let { value, done } = it.next(data);
    console.log(value, done);
  })
})

co 库的内部原理

// name.txt 的内容 age.txt
// age.txt  的内容 10
const fs = require('fs').promises;

// 代码编写更像是同步的,更友好的编码方式 (执行还是异步的)
function* read() {
  try {
    let name = yield fs.readFile('name.txt', 'utf8');
    let age = yield fs.readFile(name, 'utf8');
    return age;
  } catch (error) {
    console.log(error);
  }
}

// 手写 co,异步迭代:co 有现成的库,koa、express 的作者 tj
const co = it => new Promise((resolve, reject) => {
  // 异步迭代的核心是 => 回调函数
  function next(data) {
    let { value, done } = it.next(data);
    if (!done) {
      // 默认成功后会调用 next 方法,将结果传递到 next 函数中
      // 如果 yield 返回的不是一个 Promise 对象时,我们对 value 使用了 Promise.resolve() 进行了包装,这样就可以处理返回一个普通值时没有 then 方法的问题
      Promise.resolve(value).then(next, reject);
    } else {
      resolve(value);
    }
  }
  next();
});

// 测试:co 会将生成器的结果转成 promise
co(read())
  .then(data => console.log(data))
  .catch(err => console.log(err))
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于产品、设计、开发的问题和看法,还有技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程

了解更多

目录

  1. 1. 内容来自 > ES6+ Generator
  2. 2. 生成器对象和生成器函数
    1. 2.1. Generator.prototype.next()
    2. 2.2. Generator.prototype.return()
    3. 2.3. Generator.prototype.throw()
  3. 3. Generator 案例
    1. 3.1. 类数组转化
    2. 3.2. 单步获取质数
  4. 4. Generator 函数应用
    1. 4.1. 回调地狱问题
    2. 4.2. Generator 初步解决
    3. 4.3. co 库的内部原理