JS 迭代器和生成器

143 阅读5分钟

一、迭代器(Iterator)

迭代

  • 迭代是指重复一个过程,通常是为了生成一系列结果,在编程中,循环是最常见的迭代技术,而迭代器是一种用于在特定数据结构上进行一致迭代的替代语言构造

可迭代协议

  • 可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为

可迭代对象

  • 对象要成为可迭代对象,必须实现@@iterator方法,即对象(或者原型链上的某个对象)必须有一个键为 @@iterator 的属性
  • 原生实现的
    • for-of 循环
    • 数组解构
    • 扩展操作符
    • Array.from()
    • 创建集合
    • 创建映射
    • Promise.all() 接收由promise组成的可迭代对象
    • promise.race() 接收由promise组成的可迭代对象
    • yield*操作符,在生成器中使用

迭代器协议

  • 定义了产生一系列值,当值为有限个时,所有的值都被迭代完后,返回一个默认值

迭代器

  • 迭代器是一个对象,它定义一个序列,并在中止时返回值
  • 迭代器是按需创建的一次性对象。每个迭代器都会关联一个可迭代对象,而迭代器会暴露迭代其关联可迭代对象的API
  • 成为迭代器的条件
    • 实现一个next()方法,一个无参数的或者可以接受一个参数的函数,返回一个应当拥有以下两个属性的对象
      • done(boolean):如果迭代器有下一个值,则为false,否则为true
      • value:迭代器返回的值,done 为 true 时可省略

内置迭代器

let arr = [1, 3, 6];
const iter = arr[Symbol.iterator]();  
console.log(iter);  // Array Iterator {}

console.log(iter.next());  // {value: 1, done: false}
console.log(iter.next());  // {value: 3, done: false}
console.log(iter.next());  // {value: 6, done: false}
console.log(iter.next());  // {value: undefined, done: true}

自定义迭代器

class Counts {
    constructor(limit) {
        this.limit = limit;
    }
    
    [Symbol.iterator]() {
        let count = 1, limit = this.limit;
        return {
            next() {
                if (count <= limit) {
                    return {done: false, value: count++};
                } else {
                    return {done: true, value: undefined};
                }
            }
        };
    }
}

let count = new Counts(3);
for (let i of count) {
    console.log(i);  // 1 2 3
}

二、生成器(generator)

生成器对象

  • 生成器对象是由一个生成器函数返回的迭代器,并且符合可迭代协议和迭代器协议
  • 包含以下三种方法
    • next() 方法返回一个包含属性 done 和 value 的对象。该方法也可以通过接受一个参数用以向生成器传值
    • return() 方法返回给定的值并结束生成器
    • throw() 方法用来向生成器抛出异常,并恢复生成器的执行,返回带有 done 及 value 两个属性的对象
function* gen() {
    while(true) {
      try {
         yield 42;
      } catch(e) {
        console.log("Error caught!");
      }
    }
  }
  
  var g = gen();
  console.log(g.next()); // { value: 42, done: false }
  console.log(g.throw(new Error("Something went wrong"))); // "Error caught!"  { value: 42, done: false }
  console.log(g.next()); // { value: 42, done: false }

yield 关键字

  • yield关键字用来暂停和恢复一个生成器函数
  • yield关键字实际返回一个IteratorResult对象,它有两个属性,valuedonevalue属性是对yield表达式求值的结果,而donefalse,表示生成器函数尚未完全完成

yield* 表达式

  • yield* 表达式**用于委托给另一个generator 或可迭代对象
// 委托给其他生成器
function* generator(){
    yield* anotherGenerator();
}
function* anotherGenerator(){
    yield 1
}

var gen = generator();
console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: undefined, done: true}
// 委托给其他可迭代对象
function* generator(){
    yield* [1, 2];
}

var gen = generator();
console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 2, done: false}
console.log(gen.next()); // {value: undefined, done: true}

生成器函数

  • 允许定义一个包含自由迭代算法的函数,同时可以自动维护自己地状态。

  • 在执行时能暂停,后面又能从暂停处继续执行

    function*

    • 定义一个生成器函数 ,它返回一个 Generator 对象

      function* generator(n){
          yield n + 2;
      }
      
      var gen = generator(2);
      console.log(gen.next().value); // 4
      console.log(gen.next().value); // undefined
      

    GeneratorFunction

    • 通过GeneratorFunction构造其创建新的生成器函数对象。在JavaScript中,生成器函数实际上都是GeneratorFunction的实例对象

      // 生成器函数
      function* anotherGenerator() {
          yield 2;
          yield 3;
      }
      
      function* generator() {
          yield 1;
          yield* anotherGenerator();
          yield 4;
      }
      
      // 调用生成器函数首先返回一个Generator 
      let gen = generator(); // "Generator { }"
      
      // 首次调用 generator 的 next() 方法,执行到第一个yield出现的位置为止,并返回yield后的值
      console.log(gen.next());  // {value: 1, done: false}
      
      // 接着调用 generator 的 next() 方法,遇到 yield* ,将执行权移交给 anotherGenerator
      // 继续执行到 anotherGenerator 出现 yield 的位置,并返回yield后的值
      console.log(gen.next());  // {value: 2, done: false}
      
      // 接着调用 generator 的 next() 方法,anotherGenerator 执行到出现 yield 的位置,并返回yield后的值
      console.log(gen.next());  // {value: 3, done: false}
      
      // 接着调用 generator 的 next() 方法,anotherGenerator 生成器函数已经执行完毕,执行权交回 generator
      // 继续执行到 generator 出现 yield 的位置,并返回yield后的值
      console.log(gen.next());  // {value: 4, done: false}
      
      // generator 生成器函数执行完毕,next() 返回值中 done 为 true
      console.log(gen.next());  // {value: undefined, done: true}
      

三、异步迭代器

  • 异步迭代器同步迭代器相同,都是一个函数,并且含有一个next方法,区别在于同步迭代器next方法返回一个含有valuedone属性的对象,而异步迭代器next方法返回一个Promise对象,并且Promise对象的值为含有valuedone属性的对象
const createAsyncIterator = items => {
    const keys = Object.keys(items)
    const len = keys.length
    let pointer = 0
    return {
        next() {
            const done = pointer >= len
            const value = !done ? items[keys[pointer++]] : undefined
            return Promise.resolve({
                value,
                done
            })
        }
    }
}

const aynscIterator = createAsyncIterator([1, 2, 3])
aynscIterator.next().then(({value, done}) => {
    console.log(value, done) // 1 false
})
aynscIterator.next().then(({value, done}) => {
    console.log(value, done) // 2 false
})
aynscIterator.next().then(({value, done}) => {
    console.log(value, done) // 3 false
})
aynscIterator.next().then(({value, done}) => {
    console.log(value, done) // undefined true
})

四、异步生成器

  • 异步生成器和同步的生成器很像,但是其是async函数,内部可以使用await
async function* asyncGenerator() {
    yield await Promise.resolve(1)
    yield await Promise.resolve(2)
    yield await Promise.resolve(3)
  }
  const asyncIterator = asyncGenerator()
  async function run() {
    console.log(await asyncIterator.next()) // {"value": 1,"done": false}
    console.log(await asyncIterator.next()) // {"value": 2,"done": false}
    console.log(await asyncIterator.next()) // {"value": 3,"done": false}
    console.log(await asyncIterator.next()) // {"value": undefined,"done": true}
  }
  run()

五、总结

  • 同步生成器
    • 在ES6中引入的,它们允许函数在执行过程中暂停,并在需要时恢复执行。生成器函数使用function*语法定义,并且可以使用yield关键字来暂停函数的执行
    • 场景:dvaJS、async+await
  • 异步生成器
    • 在ES2018中引入的,它们允许在生成器函数中使用await关键字,从而处理异步操作。异步生成器函数使用async function*语法定义,并且可以使用yield关键字来暂停函数的执行,直到异步操作完成
    • 场景:处理需要逐步获取数据的异步数据流,例如从API逐页获取数据或异步任务队列,每个任务在前一个任务完成后才开始执行