前言
在我们学习ES6时,我们知道处理异步的方法有两种,分别是promise
和async-await
。promise
方法很好理解,那就是 ES6 提供出了一个promise
构造函数,我们可以直接new
一个promise
对象出来。我们就可以用这个对象解决异步问题。
这里我们就好奇了,async-await
方法虽然也借助了promise
对象,但他是怎么通过关键字来阻止代码的执行的呢?
到这里我们的铺垫就结束了,现在就该请出我们今天的主角Generator 函数
了。
Generator 函数
首先我们先来给Generator
函数做个自我介绍吧。Generator
函数是 ES6 引入的一种特殊函数,它可以通过在函数体内部使用 yield
关键字来定义一个可暂停和可恢复执行的函数。
以 ES6 的原话来讲:
语法来讲,我们可以把Generator
函数理解成一个状态机,里面封装了多个内部状态。执行Generator
函数会返回一个遍历器对象,这个对象可以依次遍历 Generator 函数内部的每一个状态。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function
关键字与函数名之间有一个星号;二是,函数体内部使用yield
表达式,定义不同的内部状态。
说了这么多,可能大家也没明白它究竟是干嘛的吧。那我们直接上一段简单的代码,看看它Generator 函数
有什么不一样。
// 函数外部使用 * 来区分与普通函数的不同
function* myGenerator() {
// 函数体内部使用 yield 关键字定义暂停点
yield 1;
yield 2;
yield 3;
}
const iterator = myGenerator();
console.log(iterator); // 输出:Object [Generator] {}
console.log(iterator.next()); // 输出:{ value: 1, done: false }
console.log(iterator.next()); // 输出:{ value: 2, done: false }
console.log(iterator.next()); // 输出:{ value: 3, done: false }
console.log(iterator.next()); // 输出:{ value: undefined, done: true }
我们来解析解析上述代码,首先它用 *
区分与普通函数的不同, 其次它用 yield
关键字作为函数执行的暂停点。当函数执行遇到了yield
关键字它则会暂停执行,最后它需要借助next
方法才能将函数继续往下执行,同时next
方法返回出一个对象,它的value
属性就是当前yield
表达式的值,而done
属性的值false
,表示遍历还没有结束。其代码执行的打印结果也正如注释所示。
看完上述代码,Generator 函数
无非也就是暂停函数的执行,也没看出和async-await
有什么关系呀!那接下来,我们将Generator 函数
与promise
结合起来,我们再看看。
与 Promise 结合
我们先来两个函数,一个为Generator 函数
,一个为普通函数:
function foo(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num * 2)
}, 1000)
})
}
function* gen() {
const num1 = yield foo(1)
const num2 = yield foo(num1)
const num3 = yield foo(num2)
return num3
}
我们在普通函数中返回了一个promise
对象,同时还增加了一个定时器。我们再和之前一样调用它。
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());
这样调用的话,我们的打印结果如下:
如上图所示的打印结果,我们next
方法每次返回出一个promise
对象,而且因为定时器的缘故,这些promise
还未resolve
就已经被打印了。那如果我们想实现一个像foo
函数一样,每次调用都乘2在返回该怎么办呢?我们将调用的方式换一种,如下代码所示:
const g = gen()
const next1 = g.next() // { value: Promise { <pending> }, done: false }
console.log('next1',next1);
next1.value.then(res1 => {
console.log('res1', res1);
const next2 = g.next(res1) // { value: Promise { <pending> }, done: false }
console.log('next2',next2);
next2.value.then(res2 => {
console.log('res2',res2);
const next3 = g.next(res2) // { value: Promise { <pending> }, done: false }
console.log('next3', next3);
next3.value.then(res3 => {
console.log('res3', res3);
console.log('next3', next3);
console.log('value', g.next().value);
})
})
})
我们在看看打印结果:
这不就是我们想要的结果吗?那上面代码是怎么做到的呢?我们来分析分析:
第一步: 声明next1
赋值为第一次next()
方法返回的对象。next1
打印的结果为一个promise
对象,在用then去接受promise
对象reslove
的结果,我们就得到了2
这个结果。
第二步:声明next2
赋值为第二次next()
方法返回的对象。next2
打印的结果也为一个promise
对象,和next1
不同的就是第二次next
方法执行有一个参数res1
, 但这个res1
不是next
的参数,而是函数foo
的参数。这样我们就完成了第二次next
,同时还得到了4
这个结果。
第三步:递归,重复第二次的操作。
这样一步一步的在then方法中套递归,我们就可以完成乘二的操作。我们再来看看async-await
的写法
async function gen() {
const num1 = await foo(1)
const num2 = await foo(num1)
const num3 = await foo(num2)
return num3
}
gen().then(res => {
console.log(res); //8
})
这样看起来Generator 函数
和async-await
是不是很像,我们在将上述调用方法优化成一个递归调用,我们不就实现了和async-await
一样的效果吗?
所以现在来回答最终的问题:Generator 函数是个什么函数呢
?
Generator 函数是和 async/await 一样,都提供了一种在异步代码中控制流程的方式的函数。它使我们可以更加灵活地控制函数的执行流程,实现惰性计算、异步编程、流程控制。