generator也是解决异步的一种方式,这对我们后续学习async源码会很有帮助
语法
只要给一个函数关键字后面添加一个星号*,那么这个函数就被称之为生成器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一定为true
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,实际上也果真如此
lua
复制代码
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* 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() {
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后执行,能做到这个就相当于解决了异步问题
javascript
复制代码
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,如下
javascript
复制代码
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
需要安装依赖,这是别人自行封装的
csharp
复制代码
npm init -y // 别忘了初始化
npm i co
写法如下,写法比thunk优雅,同样需要异步函数内置Promise
javascript
复制代码
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了