先看例子
用生成器生成一个 Fibonacci 数列,并获取其前十个数字:
function * fib(){
yield 1
yield 1
let a = 1
let b = 1
let tmp
while(true){
yield a + b
tmp = a;
a = b;
b = tmp + b;
}
}
const g = fib();
Array.from({length : 10}).forEach(_ => {
const num = g.next().value
console.log(num);
})
// 1
// 1
// 2
// 3
// 5
// 8
// 13
// 21
// 34
// 55
生成器中重要的next方法
生成器函数具有惰性执行的特性,每次执行到一个 yield 语句时,生成器会被“冻结“,直到下一个 next 方法被调用:
function * ge(){
yield 1
console.log('execute');
yield 1
}
const g = ge();
g.next() // nothing
g.next() // execute
next 方法会返回一个固定结构的对象: {value : a, done : b} ,value 表示 yield 抛出的值, done 表示是否所有的 yield 均被执行完成:
function * ge(){
yield 1
}
const g = ge();
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: undefined, done: true }
next 方法允许我们传入一个参数,这个参数会被作为生成器函数中 yield 的返回值:
function * ge(value){
while(true){
const step = yield value ++
if(step){
value += step
}
}
}
const g = ge(0);
console.log(g.next().value); // 0
console.log(g.next(10).value); // 11
console.log(g.next().value); // 12
需要注意的是,当我们执行第一个 g.next() 时,生成器中只执行了 yield value ++ 而不是执行了 const step = yield value ++ ,即如果我们期望在调用第一个 next() 时就将 step 设置为10是做不到的:
function * ge(value){
while(true){
const step = yield value ++
if(step){
value += step
}
}
}
const g = ge(0);
console.log(g.next(10).value); // 0
console.log(g.next().value); // 1
console.log(g.next().value); // 2
因为第一次执行时没有执行完整语句 const step = yield value ++ ,所以我们在第一次传入的参数 10 没有被保存起来,它不会进入第二次 next 执行的上下文,因此第一次传入 next 的参数没有任何作用。
反观:
function * ge(value){
while(true){
const step = yield value ++
if(step){
value += step
}
}
}
const g = ge(0);
console.log(g.next().value); // 0
console.log(g.next(10).value); // 11
console.log(g.next().value); // 12
在调用第二个 next 时传递了一个变量 10 ,它会作为前一条 yield 语句的返回值赋值给 step ,从而打印出不同的结果。
手动实现一个 async/await
await 其实可以看作 yield 的一个语法糖,借助生成器的强大功能,我们可以手动实现一个和 async/await :
先来看下一个传统的 async/await 语法:
function getData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(1)
}, 2000)
})
}
const req = async () => {
const res = await getData()
console.log({ res });
const res2 = await getData()
console.log({ res2 });
}
async 其实起到的是包装并执行的作用,它会转换函数为生成器并执行它,类似于如下这样:
function* req() {
const res = yield getData()
console.log({ res });
const res2 = yield getData()
console.log({ res2 });
}
executer(req)
我们用 executer 函数执行生成器,将执行的结果通过 next 函数传递给 res , res2 对象,从而实现 async/await 语句:
function executer(generater) {
const g = generater()
const g_status = g.next() // 执行 yield getData()
while (!g_status.done) {
const p = g_status.value // getData()返回Promise对象
p.then(res => {
g.next(res) // 将前一次的执行结果作为 yield 的返回值传递回生成器
})
}
}
当我们信心满满的运行上面的代码时,会发生程序陷入了死循环,因为异步方法 .then 想要执行时被 while 语句阻塞了,因此我们不能使用 while 语句,使用递归是一个更好的做法:
function executer(generater) {
const g = generater()
const gStatus = g.next()
function exec(status){
if(status.done){ // done为true,所有yield执行完成
return
}
const p = status.value // Promise对象
p.then(res => {
const nextStatus = g.next(res) // 将前一次的执行结果作为 yield 的返回值传递回生成器,返回执行完 next 后的生成器状态
exec(nextStatus) // 将生成器状态传递给下一个递归函数,如果done为 true ,则终止执行,否则继续调用下一个next
})
}
exec(gStatus)
}
使用我们写的 async/await:
function getData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(1)
}, 2000)
})
}
function* req() {
const res = yield getData()
console.log({ res });
const res2 = yield getData()
console.log({ res2 });
}
function executer(generater) {
const g = generater()
const gStatus = g.next()
function exec(status){
if(status.done){
return
}
const p = status.value
p.then(res => {
const nextStatus = g.next(res)
exec(nextStatus)
})
}
exec(gStatus)
}
executer(req)
// { res: 1 }
// { res2: 1 }