语法
function* name([param[, param[, ... param]]]) { statements }
- name:函数名
- param:要传递给函数的一个参数的名称,一个函数最多可以有 255 个参数。
- statements:普通 JS 语句。
调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器对象。在生成器函数内部可以通过yield
关键字来中断代码的执行,迭代器每次执行都会执行到下一次 yield 或者 return。
生成器函数不能作为构造器使用。
每次迭代的返回值是一个对象,结构为{value: string; done: boolean}
value 是执行到的 yield 表达式的结果,done 表示迭代器是否迭代结束,false 表示迭代还在继续,true 表示迭代完成。
通过调用迭代器对象的 next 方法可以控制生成器函数的执行,next 方法可以接受参数作为当次 yield 表达式的计算结果。
function* gen(x: number): Generator< number, number, number > { const y = yield x + 10; const z = yield x * y; return z; } const g = gen(2); console.log(g.next(1000)); // { value: 12, done: false } console.log(g.next(5)); // { value: 10, done: false } console.log(g.next(22)); // { value: 22, done: true } 复制代码
注意,如果 yield 的结果有赋值操作需要在 TS 中生成器函数需要标注生成器函数的类型。
interface Generator< T = unknown, TReturn = any, TNext = unknown > extends Iterator<T, TReturn, TNext> { // NOTE: 'next' is defined using a tuple to ensure // we report the correct assignability errors in all places. next(...args: [] | [TNext]): IteratorResult<T, TReturn>; return(value: TReturn): IteratorResult<T, TReturn>; throw(e: any): IteratorResult<T, TReturn>; [Symbol.iterator](): Generator<T, TReturn, TNext>; } 复制代码
由于每一步 yield 的结果并不会直接用于下一步计算,而是返回值的 value, 真正用于下一步计算的结果是 next 填写的参数。
第一次next 传的参数总是无效的,因为第一次中断依赖的变量只能通过形参级全局变量,没有更早的yield 中断。
如果用的是 yield*
,则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。
function* anotherGenerator(i: number) { yield i + 1; yield i + 2; yield i + 3; } function* generator(i: number){ yield i; yield* anotherGenerator(i);// 移交执行权 yield i + 10; } var gen = generator(10); console.log(gen.next().value); // 10 console.log(gen.next().value); // 11 console.log(gen.next().value); // 12 console.log(gen.next().value); // 13 console.log(gen.next().value); // 20 复制代码
练习
分析下面的代码的输出结果
function* iterArr(arr) { if (Array.isArray(arr)) { for (let i = 0; i < arr.length; i++) { yield* iterArr(arr[i]); } } else { yield arr; } } var arr = ['a', ['b', 'c'], ['d', 'e']]; for (var x of iterArr(arr)) { console.log(x); } var arr = ['a', ['b', ['c', ['d', 'e']]]]; var gen = iterArr(arr); arr = [...gen]; 复制代码
参考答案
拓展
众所周知,async 是目前最好的异步方案,是因为 async 写法非常清晰明了,但是能保证其异步的特性,早起没有 async 的时候社区里面就有使用自执行函数+生成器函数+Promise 实现了类 async 的效果,最终也是将这种思想成功的带进了 ES2017。
那么具体是怎么实现的呢,我们来逐步实现一下,首先是异步代码,我们先按照 async 的写法来看一下代码
function req1() { return new Promise((resolve) => { setTimeout(() => resolve(2), 1000) }) } function req2() { return new Promise((resolve) => { setTimeout(() => resolve(22), 2000) }) } async function exec() { const res1 = await req1(); const res2 = await req2(); return res1 + res2; } exec().then(console.log) // 22 复制代码
生成器版本是借助了一个第三方的库co
来实现了自动执行,co 函数接收一个生成器函数,返回值是一个 promise 对象。
const co = require('co'); function* exec() { const res1 = yield req1() const res2 = yield req2() return res1 + res2 } co(exec).then(console.log) // 22 复制代码
Generator 函数就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。
两种方法可以做到这一点:(1)回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权;(2)Promise 对象。将异步操作包装成 Promise 对象,用 then 方法交回执行权。
co 函数库其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个库。