Generator 和异步迭代
Generator 函数
普通函数只可以返回一个值,或者不返回任何值(实际返回 undefined)
Generator 函数可以返回('yield')多个值, 它们可以与 iterator 配合使用
要创建 Generator 函数,需要用一个特殊的语法function *
javascript1function* generateSequence() { 2 yield 1 3 yield 2 4 return 3 5}
当调用generator函数时,并不会运行其代码,而是返回一个[object Generator]的特殊对象,我们称其为 “generator object”
javascript1function* generateSequence() { 2 yield 1 3 yield 2 4 return 3 5} 6const a = generateSequence() 7console.log(a) //[object Generator]
到目前为止,上面的代码还没有运行,我们拿到的是generator对象。

generator 对象的主要方法是 next(),当调用该方法时,它就会开始运行,执行到最近的 yield语句,然后函数暂停,将yield的值返回出去。
next()的结果会返回一个对象,该对象有两个属性
- value:yield 后的值
- done:如果 generator 执行完成,则返回
true,如果没有则返回false
javascript1function* generateSequence() { 2 yield 1 3 yield 2 4 return 3 5} 6const a = generateSequence() 7console.log(a) 8console.log(a.next()) // {value:1,done:false}
截止目前,我们获取到了第一个值,现在代码处于这里

当我们再次调用 next方法,代码就会从当前的位置继续往下 yield,直到 done为 true为止
javascript1console.log(a.next()) // {value:2,done:false} 2console.log(a.next()) // {value:3,done:true}
现在代码已经执行完成了,done的状态为true,如果继续往下执行,会返回 value为undefined的对象
javascript1console.log(a.next()) // {value:undefined,done:true}
Generator 可迭代
每个generator 对象都有 next方法,这意味着它是可迭代的(iterable),他内置了iterable接口。
javascript1function* generateSequence() { 2 yield 1 3 yield 2 4 return 3 5} 6const a = generateSequence() 7 8for (let item of a) { 9 console.log(item) 10} 11// 1 12// 2
使用 for 循环时,会默认调用 next()方法,然后打印出value
....上面的例子中,并没有返回 3,这是因为当done为 true时,for..of循环会忽略 value
如果我们希望能够获得3 ,则必须使用 yield返回
javascript1function* generateSequence() { 2 yield 1 //{value:1,done:false} 3 yield 2 //{value:2,done:false} 4 yield 3 //{value:3,done:false} 5} 6const a = generateSequence() 7 8for (let item of a) { 9 console.log(item) 10} 11// 1 12// 2 13// 3
由于 generator对象部署了iterable接口,所以我们可以使用展开符去展开它
javascript1function* generateSequence() { 2 yield 1 //{value:1,done:false} 3 yield 2 //{value:2,done:false} 4 yield 3 //{value:3,done:false} 5} 6const a = generateSequence() 7const arr = [...a] 8console.log(arr) // [1, 2, 3]
使用 Generator 迭代
下面我们为对象手写一个 iterable 接口
javascript1let range = { 2 from: 1, 3 to: 5, 4 // for..of range 在一开始就调用一次这个方法 5 [Symbol.iterator]() { 6 // ...它返回 iterator object: 7 // 后续的操作中,for..of 将只针对这个对象,并使用 next() 向它请求下一个值 8 return { 9 current: this.from, 10 last: this.to, 11 // for..of 循环在每次迭代时都会调用 next() 12 next() { 13 // 它应该以对象 {done:.., value :...} 的形式返回值 14 if (this.current <= this.L) { 15 return { 16 value: this.current++, 17 done: false, 18 } 19 } 20 return { 21 done: true, 22 } 23 }, 24 } 25 }, 26} 27 28for (let item of range) { 29 console.log(item) // 1 2 3 4 5 30}
上面代码主要做了如下工作
- 给对象设置
[Symbol.iterator]属性,这个属性是一个函数 - 该函数返回一个
iterator object,拥有next方法 next方法返回包含value和done两个属性的对象
由于 Generator函数的语法糖可以帮助返回包含 value 和 done两个属性的对象,所以我们可以对其进行改写
javascript1let range = { 2 from: 1, 3 to: 5, 4 *[Symbol.iterator]() { 5 for (let i = this.from; i <= this.to; i++) { 6 yield i 7 } 8 }, 9} 10 11const generator = range[Symbol.iterator]() 12console.log(generator.next()) // 1 13console.log(generator.next()) // 2 14console.log(generator.next()) // 3 15console.log(generator.next()) // 4 16console.log(generator.next()) // 5
当
for..of循环时,会默认调用[Symbol.iterator]的next方法,然后取其返回对象的value值,直到done为true为止
Generator 函数能够帮助生成 iterator接口所需要的内容:
- 一个拥有
next方法的对象(此时为generator对象) - 该对象
next方法可以返回{done:boolean,value:any}结构的对象
所以Generator可以看作 iterator接口的语法糖
Generator 组合
使用 yield *指令可以将执行委托给另一个 generator,比如下面我们使用这个语法来将一个 generator组合到另一个 generator中
javascript1function* generateSequence(start, end) { 2 for (let i = start; i <= end; i++) yield i 3} 4 5function* generateAll() { 6 yield* generateSequence(1, 6) // * 7 for (let i = start; i <= end; i++) yield i // *行与这行代码效果是一样的 8} 9const g = generateAll() 10for (let item of g) { 11 console.log(item) // 1 2 3 4 5 6 7 8 9 10 12}
Generator 传递参数
yield不仅可以向外返回结果,还可以将外部的值传递到 generator内部。
调用 generator.next(arg),我们就可以将参数arg传递到 generator 内部,这个 arg 参数会变成yield的结果。
javascript1function* gen() { 2 // 向外部代码传递一个问题并等待答案 3 let result = yield '2 + 2 = ?' // (*) 4 5 alert(result) 6} 7 8let generator = gen() 9 10console.log(generator.next()) // 结果为 {done: false,value: "2 + 2 = ?"} 11console.log(generator.next(4)) // 会弹出 4 但结果为 {done: true,value: undefined}
yield 会将外部传递的参数 4 传递给 result,然后通过弹框打印出来。此时 yield 已执行结束,所以返回给外部的 yield 值为{done: true,value: undefined}

- 第一次调用
generator.next()时,会经过yield,此时往外部返回yield值。(如果此时传递 arg 参数,那么会被忽略)它往外部返回第一个yield结果{done: false,value: "2 + 2 = ?"} - 此时,在第
*行,generator执行暂停 - 当调用
generator.next(4)时,generator恢复,并获取外部传入的 4 作为result:let result = 4
我们可以发现,generator 是通过yield来向外部传递值,外部则通过调用 next方法向 generator函数内部传递参数来交换结果。
根据上面的结论,我们再来看一个例子
javascript1function* gen() { 2 let ask1 = yield '2 + 2 = ?' 3 4 alert(ask1) // 4 5 6 let ask2 = yield '3 * 3 = ?' 7 8 alert(ask2) // 9 9} 10 11let generator = gen() 12 13console.log('yield', generator.next()) //yield {done: false,value: "2 + 2 = ?"} 14console.log('yield', generator.next(4)) //alert 4 yield {done: false,value: "3 * 3 = ?"} 15console.log('yield', generator.next(9)) //alert 9 yield {done: true,value: undefined}
我们可以发现:
- 一共有两个
yield关键字,那么如果希望传递给外部的yield有value,则需要调用两次next方法 - 当调用第二次
next并传递arg时,会把arg当成结果传递给ask1。然后发现后面还有yield,就又往外传递。此时就相当于内外部互相传递了信息 - 当调用第三次
next时,由于没有yield关键字了,所以就只是向内部传递一下arg,往外透出的是{done: true,value: undefined}
执行图:

- 第一个
.next()启动了 generator 的执行……执行到达第一个yield。 - 结果被返回到外部代码中。
- 第二个
.next(4)将4作为第一个yield的结果传递回 generator 并恢复 generator 的执行。 - ……执行到达第二个
yield,它变成了generator.next(4)调用的结果。 - 第三个
next(9)将9作为第二个yield的结果传入 generator 并恢复 generator 的执行,执行现在到达了函数的最底部,所以返回done: true。
这个过程就像“乒乓球”游戏。每个 next(value)(除了第一个)传递一个值到 generator 中,该值变成了当前 yield 的结果,然后获取下一个 yield 的结果。
generator.throw
外部代码可以通过generator.next方法将值传递给 generator,作为yield的结果,也可以在yield的代码行抛出一个 error,
如果想这样做,我们需要调用generator.throw来将 error抛到对应的yield所在行
javascript1function* gen() { 2 try { 3 let result = yield '2 + 2 = ?' // (1) 4 5 alert('执行没有到这里,因为上面抛出了异常') 6 } catch (e) { 7 alert(e) // 显示这个 error 8 } 9} 10 11let generator = gen() 12 13generator.next() 14 15generator.throw(new Error('这是一个错误')) // (2)
- 当第一次调用
generator.next()时,会在第(1)行yield一个结果出来,然后暂停 - 外部调用
generator.throw将错误抛给内部时,此时generator继续执行,执行到let result,并被try..catch捕获到,抛给外部一个错误,后面的代码不会被继续执行
如果我们没有在generator内部捕获它,那么这个错误就会从 generator掉出到外部代码中。
javascript1function* gen() { 2 let result = yield '2 + 2 = ?' // (1) 3 alert('执行没有到这里,因为上面抛出了异常') 4} 5 6let generator = gen() 7try { 8 generator.next() 9 generator.throw(new Error('这是一个错误')) // (2) 10} catch (error) { 11 alert(error) 12}
下面的代码效果是一样的。
小结
- Generator 函数通过
function*关键字创建 - 在 generator 内部,使用
yield关键字 - 外部和
generator内部可以通过next/yield调换结果
异步迭代
迭代器是采用 iterator接口的,如果希望给一个未内置迭代接口的对象设置iterator 接口,我们需要完成以下步骤:
- 给对象设置
[Symbol.iterator]属性,这个属性指向一个 function - 该
function需要返回一个具有next方法的对象,我们称之为iterator object - 该对象的
next方法返回具有done:boolean和value:any属性的对象
以下是例子
javascript1let range = { 2 from: 1, 3 to: 5, 4 //for..of 循环开始时,会调用这个方法,获取 iterator object 5 [Symbol.iterator]() { 6 return { 7 current: this.from, 8 last: this.to, 9 // for..of 每次循环,都会调用 next 方法,直到 done 为 true 为止 10 next() { 11 return this.current <= this.last 12 ? // next 方法返回的对象必须为{done:boolean,value:any}的格式 13 { done: false, value: this.current++ } 14 : { done: true } 15 }, 16 } 17 }, 18} 19for (let item of range) console.log(item) // 1 2 3 4 5
如果我们希望能够采用异步迭代,比如说 setTimeout 的延迟之后迭代,就需要用到异步迭代。
要让上面的对象拥有异步迭代的能力,我们需要做以下调整
[Symbol.iterator]修改为[Symbol.asyncIterator]next方法返回一个 Promise,并且状态为fulfilled- 使用 for await ..进行循环
下面是修改后的代码
javascript1let range = { 2 from: 1, 3 to: 5, 4 [Symbol.asyncIterator]() { 5 return { 6 current: this.from, 7 last: this.to, 8 // 这里的 async 是让return 的值变成 promise 9 async next() { 10 // 这里使用 await 阻塞,模拟延迟效果 11 await new Promise((resolve) => setTimeout(resolve, 1000)) 12 return this.current <= this.last ? { done: false, value: this.current++ } : { done: true } 13 }, 14 } 15 }, 16} 17// 配合 for await 必须用 async 18;(async () => { 19 for await (let item of range) { 20 console.log(item) 21 } 22})()
- next 方法可以不是
async,这里只是为了让next必须返回promise而写的语法糖 - 为了让异步迭代生效,其内部必须具有
Symbol.asyncIterator方法 - 使用
for await迭代,需要用上async关键字
以下是两者对比差异的表格:
| Iterator | 异步 iterator | |
|---|---|---|
| 提供 iterator 的对象方法 | Symbol.iterator | Symbol.asyncIterator |
next() 返回的值是 | 任意值 | Promise |
| 要进行循环,使用 | for..of | for await..of |
异步 generator
generator 可以很方便地帮助我们生成 iterator 所需要的东西:
——通过*关键字生成generator对象,内部包含 next方法
——通过 yield抛出 具有done和value属性的对象
上面的代码可以修改成这样
javascript1let range = { 2 from: 1, 3 to: 5, 4 async *[Symbol.asyncIterator]() { 5 for (let i = this.from; i <= this.to; i++) { 6 await new Promise((resolve) => setTimeout(resolve, 1000)) 7 yield i 8 } 9 }, 10} 11// 配合 for await 必须用 async 12;(async () => { 13 for await (let item of range) { 14 console.log(item) // 1 2 3 4 5 15 } 16})()
此时,generator.next()返回一个 promise,且它是一个异步的函数
javascript1;(async () => { 2 const asyncGenerator = range[Symbol.asyncIterator]() 3 const result = await asyncGenerator.next() // 返回Promise Object {done:false,value:1} 4 console.log(result) // {done:false,value:1} 5})()