详聊es6的Generator
generator
也是解决异步的一种方式,这对我们后续学习async
源码会很有帮助,尽管generator
在异步发展史中仅仅是昙花一现,但是既然要对线面试官,我们还是得把它给搞明白
语法
只要给一个函数关键字后面添加一个星号*
,那么这个函数就被称之为生成器generator
函数,此时并无特殊之处,我们现在可以去浏览器打印看看这个函数的执行结果长什么样
里面有个constructor
,说明这样写就相当于new
了一个生成器函数,并且,里面有next
关键字,这个关键字是放在原型身上的,说名我们的实例对象是可以去写.next
的
既然如此,那我把它的执行结果赋值给另一个参数gen
,然后让gen
去.next
,我们试试看。当然我们需要在generator
函数里面写上yield
关键字,yield
是产出的意思
function* foo () {
yield 'a'
yield 'b'
yield 'c'
return 'ending'
}
let gen = foo() // 非执行,相当于new了一个实例对象,这就是*作用
console.log(gen.next()); // { value: 'a', done: false }
console.log(gen.next()); // { value: 'b', done: false }
console.log(gen.next()); // { value: 'c', done: false }
console.log(gen.next()); // { value: 'ending', done: true }
我们打印这个实例对象再去.next
的执行结果居然是个对象,里面有value
和done
两个key
这个value
值为a
表示的是,执行第一个yield
,也就是a
,而这个done
为false
表示的是整个generator
函数还未执行完毕,因此如此往复,执行最后一个就是return
出的value
值,并且执行完毕
next
对应的yield
,其done
一定为false
function* foo () {
yield 'a'
yield 'b'
yield 'c'
yield 'ending'
}
let gen = foo()
console.log(gen.next()); // { value: 'a', done: false }
console.log(gen.next()); // { value: 'b', done: false }
console.log(gen.next()); // { value: 'c', done: false }
console.log(gen.next()); // { value: 'ending', done: false }
所以到这里你其实就明白,
next
只是给yield
下指令,有了next
就执行一个yield
如果我们继续让其next
,此时已经没有对应的yield
让你去执行了,因此不出所料value
就是undefined
,实际上也果真如此
function* foo () {
yield 'a'
yield 'b'
yield 'c'
yield 'ending'
}
let gen = foo()
console.log(gen.next()); // { value: 'a', done: false }
console.log(gen.next()); // { value: 'b', done: false }
console.log(gen.next()); // { value: 'c', done: false }
console.log(gen.next()); // { value: 'ending', done: false }
console.log(gen.next()); // { value: undefined, done: true }
执行
generator
函数就不是传统意义的执行,它的执行是给我带来一个实例对象(也称之为迭代对象
),并且实例对象去.next
就是去返回里面的yield
,一个next
返回一个yield
,done
表示的是generator
是否执行完毕,而return
可有可无,return
的值就是done
为true
的值既然是实例对象也称之为迭代对象,里面必然是有迭代属性的,可以进行迭代
小试牛刀🌰
function* foo () {
var o = 1
yield o++ // 相当于o = o, o = o + 1
yield o++
yield o++
}
let gen = foo()
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
理清这个之前,我们需要先明白o++
和++o
的区别
o++:先读值后自增
++o:先自增后读值
因此执行第一个yield
的时候,返回o
的值,就是1,这个时候还有个自增的操作去执行,因此o
变成了2,然后执行第二个yield
时,就是读值o为2……
改巴改巴
function* foo () {
var o = 1
yield ++o // 相当于o = o + 1, o = o
yield o++
yield o++
}
let gen = foo()
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
第一个++o
就会先自增后读值,yield
返回o就是2……
这个东西我改巴改巴
function* foo () {
var o = 1
yield o++
yield o++
yield o++
}
let gen = foo()
console.log(gen.next()); // { value: 1, done: false }
let gener = foo()
console.log(gener.next()); // { value: 1, done: false }
两个实例对象,互不影响,合乎逻辑!
继续改巴改巴
function* foo () {
var o = 1
yield o++
yield
yield o++
}
let gen = foo()
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: undefined, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: undefined, done: true }
这个也很好理解!执行next
就相当于返回yield
后面的值,既然yield
后面啥也没有,就是undefined
,而后面再去读o
的值,就第一个o++
之后了,因此是2
小试牛刀🌰🌰
function* g() {
let a = 1
let b = yield a++
console.log(b);
let c = yield a++
}
let gen = g()
console.log(gen.next());
console.log(gen.next());
// 输出如下:
// { value: 1, done: false }
// undefined
// { value: 2, done: false }
分析下:这里和上面的🌰很不同,因为这里还给yield
赋值给别人了,我们其实是不清楚yield
赋值是什么样的,很多人会误以为b就是2,因为代码从右往左执行,yield a++
对应的第一个next
肯定是a = 1
,但是这时再去执行a = a + 1
,会误会为b就是2,其实yield
默认一定是undefined
,yield
自身的值一定是下一个next
里面传的参数
function* g() {
let a = 1
let b = yield a++
console.log(b);
let c = yield a++
}
let gen = g()
console.log(gen.next());
console.log(gen.next(2)); // next的参数,用于指定被我触发的yield的执行结果
// 输出如下:
// { value: 1, done: false }
// 2
// { value: 2, done: false }
第一个yield
的值,是下个next
传进来的参数
改巴改巴
function* g() {
let a = 1
let b = yield a++
console.log(b);
let c = yield a++
}
let gen = g()
console.log(gen.next());
console.log(gen.next(3)); // next的参数,用于指定被我触发的yield的执行结果
console.log(gen.next(2));
// 输出如下:
// { value: 1, done: false }
// 3
// { value: 2, done: false }
// { value: undefined, done: true }
其实就是来误导你的,本身generator
就是两个yield
,一个next
对应一个yield
,因为最后一个next
没有相应的yield
与之匹配,因此输出undefined
,并且执行完毕的状态
继续改巴改巴,这是重点戏
function* g() {
let a = 1
let b = yield a++
console.log(b);
let c = yield a++
console.log(a);
console.log(c);
}
let gen = g()
console.log(gen.next());
console.log(gen.next(3)); // next的参数,用于指定被我触发的yield的执行结果
console.log(gen.next(2));
// 输出如下:
// { value: 1, done: false }
// 3
// { value: 2, done: false }
// 3
// 2
// { value: undefined, done: true }
总共就2个yield
,这里还有3个next
,因此最后一个next
一定是undefined
,并且执行完毕,既然这里要说generator
执行完毕,里面的log
打印语句也会顺带执行完毕,因此这个两个打印在两个next
之间,是合乎逻辑的,先打印b
为3,这个b
就是第一个yield
的值,而这个yield
的值是根据下一个next
的参数决定的,如果第二个next
没有参数,那么这个b
就是undefined
,这里传了3,那么就是b = 3
,然后再是打印a
,到了第二个yield
时,已经执行了两个a++
了,因此a
也是3,最后再是打印c,c就是第二个yield
的值,这个值就是第三个next
传入的参数,是2,没有问题~
再来一个,这个也很有意思
function* g() {
yield 1
yield 2
yield 3
yield 4
yield 5
return 6
}
for (let val of g()) { // for of就是迭代具有迭代器属性的东西 也就是自带next
console.log(val); // 1 2 3 4 5
}
上面早就说了,generator
的执行就是一个实例对象,而实例对象也被称之为迭代对象,就是因为自身拥有迭代属性,因此我们可以用for of
去遍历它,这个时候肯定有小伙伴们问了,不应该是遍历.next
吗,对,没错,这个for of
自身就是去迭代拥有迭代属性的东西,迭代的时候自带next
再提一句,for of
碰到了return
是不会执行的,因此最终输出1 - 5
异步
利用generator
去解决异步,最有名的就是thunk
和co
这两个模块了
一个情景,两个定时器,让a先执行,b后执行,能做到这个就相当于解决了异步问题
function a () {
setTimeout(() => {
console.log('a');
}, 1000)
}
function b () {
setTimeout(() => {
console.log('b');
}, 500)
}
thunk
这个写法需要给异步函数传入next,如下
function a (next) {
setTimeout(() => {
console.log('a');
next()
}, 1000)
}
function b (next) {
setTimeout(() => {
console.log('b');
next()
}, 500)
}
function c (next) {
console.log('c');
next()
}
function* g () {
yield a
yield b
yield c
}
function run (fn) {
let gen = fn()
function next(err, data) {
let result = gen.next(data)
if (result.done) return
result.value(next)
}
next()
}
run(g) // a b c
其实阮一峰老师这里写的是结合Promise,如下
function a () {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('a');
resolve()
}, 1000)
})
}
function b () {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('b');
resolve()
}, 500)
})
}
function* g () {
yield a()
yield b()
}
let gen = g()
let result = gen.next()
result.value.then(value => {
gen.next()
})
co
需要安装依赖,这是别人自行封装的
npm init -y // 别忘了初始化
npm i co
写法如下,写法比thunk优雅,同样需要异步函数内置Promise
var co = require('co')
function a () {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('a');
resolve()
}, 1000)
})
}
function b () {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('b');
resolve()
}, 500)
})
}
function* g () {
yield a()
yield b()
}
co(g).then(() => {
console.log('generator执行完毕');
})
总结
generator
可以分段执行,可以暂停- 可以控制每个阶段的返回值
- 可以知道是否执行完毕
- 可以借助
Thunk
和co
处理异步,但是写法复杂,所以generator
函数的意义就是为了打造async
,await
最后
generator
处理异步其实还不如Promise
,毕竟用法上你都需要那些异步函数内置Promise
了
如果你对春招感兴趣,可以加我的个人微信:
Dolphin_Fung
,我和我的小伙伴们有个面试群,可以进群讨论你面试过程中遇到的问题,我们一起解决
另外有不懂之处欢迎在评论区留言,如果觉得文章对你学习有所帮助,还请”点赞+评论+收藏“一键三连,感谢支持!