1.什么是Generator函数?
Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。
Generator函数有多种理解角度。从语法上,首先可以把它理解成,Generator函数一个状态机,封装了多个内部状态。
执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象生成函数。
形式上,Generator函数是一个普通函数,但是有两个特征。一是,function
关键字与函数名之间有一个星号,二是,函数体内部使用yield
语句,定义不同的内部状态。(yield语句在英语里的意思就是“产出”)
// 这里定义了一个Generator 函数fn
function*fn(){
yield 1;
yield 2;
yield 3;
}
2.Generator函数的调用
在一篇前端中迭代器的理解和手动封装一个迭代器我们介绍了迭代,而Generator函数就是一个可迭代的函数。调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后每次调用遍历器对象的next
方法,就会返回一个有着value
和done
两个属性的对象。value
属性表示当前的内部状态的值,是yield
语句后面那个表达式的值;done
属性是一个布尔值,表示是否遍历结束。
function*fn(){
yield 1;
yield 2;
yield 3;
}
let f = fn();
console.log(f.next()) // {value: 1, done: false}
console.log(f.next()) // {value: 2, done: false}
console.log(f.next()) // {value: 3, done: false}
console.log(f.next()) // {value: undefined, done: true}
- next 方法的作用是分阶段执行 Generator 函数。每次调用 next 方法,会返回一个对象,表示当前阶段的信息( value 属性和 done 属性)。
- value 属性是 yield 语句后面表达式的值,表示当前阶段的值;
- done 属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段(done为false 继续执行)
3.基于Generator 函数封装一个async函数
通过上面的了解,大家也大致熟悉了Generator 函数,每次的next方法的执行就是返回yield语句后面的值或者表达式的值,基于这我们来把上面的fn给改写下。
function*fn(){
yield new Promise((resolve,reject)=> {
setTimeout(()=> {
console.log(1)
resolve()
},500)
});
yield new Promise((resolve,reject)=> {
setTimeout(()=> {
console.log(2)
resolve()
},500)
});
yield new Promise((resolve,reject)=> {
setTimeout(()=> {
console.log(3)
resolve()
},500)
});
}
let f = fn();
for(let fn of f){ // 注意 我这里是用的for of来调用
console.log(fn)
}
来看下执行的结果:
当我们通过for of来调用执行的时候,它是一次执行完,很明显没有达到我们想要的依次执行,上一个执行才执行下一个。
这时我们就需要自己来封装一个co方法来实现依次调用的过程。
function*fn(){
yield new Promise((resolve,reject)=> {
setTimeout(()=> {
console.log(1)
resolve()
},500)
});
yield new Promise((resolve,reject)=> {
setTimeout(()=> {
console.log(2)
resolve()
},500)
});
yield new Promise((resolve,reject)=> {
setTimeout(()=> {
console.log(3)
resolve()
},500)
});
}
co(fn);
function co(fn){
let f = fn() // f是fn()执行的结果 也就是一个可迭代的
next()
function next(){
let result = f.next(); // 上一个执行完毕才执行下一个
console.log(result)
}
}
执行结果:
此时我们可以拿到第一个执行的结果,还有一个done属性,也就表示这个执行的结果,
通过这个属性我们可以判断有没有执行完毕;所以此时我们只用来进行对这个done属性判断他的true或者false就可以
得知执行的结果和是否开始执行下一个;
function*fn(){
yield new Promise((resolve,reject)=> {
setTimeout(()=> {
console.log(1)
resolve()
},500)
});
yield new Promise((resolve,reject)=> {
setTimeout(()=> {
console.log(2)
resolve()
},500)
});
yield new Promise((resolve,reject)=> {
setTimeout(()=> {
console.log(3)
resolve()
},500)
});
}
co(fn);
function co(fn){
let f = fn()
next()
function next(){
let result = f.next();
if(!result.done){
// 上边一个异步走完 在执行下一个异步
result.value.then(()=> { //上一个异步执行是否完毕我们可以通过是否调用.then方法判断
next() // 执行完之后我们来调用下一个异步执行方法
})
}
console.log(result)
}
}
其执行结果:
到这里,我们的一个简单的async异步函数就已经封装好了,但是有些时候上一个异步执行完会传递一些数据到下一个
异步来使用,我们在来完善一下。
function*fn(){
yield new Promise((resolve,reject)=> {
setTimeout(()=> {
console.log('a')
resolve(1)
},500)
});
yield new Promise((resolve,reject)=> {
setTimeout(()=> {
console.log('b')
resolve(2)
},500)
});
yield new Promise((resolve,reject)=> {
setTimeout(()=> {
console.log('c')
resolve(3)
},500)
});
}
co(fn);
function co(fn){
let f = fn()
next()
function next(data){
let result = f.next();
if(!result.done){
// 上边一个异步走完 在执行下一个异步
result.value.then((info)=> { //上一个异步执行是否完毕我们可以通过是否调用.then方法判断
console.log(info,data)
next(info) // 执行完之后我们来调用下一个异步执行方法
})
}
}
}
其执行结果:
我们可以看到第一次的时候data是没有的,后面的data就是其上次执行的结果。
总结: generator函数它是我们目前所用的async /await的前身,而async /await又是 generator函数的语法糖。目前
generator函数基本已经废弃掉了,很少的使用也是在封装一些源码的时候才会使用,这里主要就是通过generator函数
来让大家更加进一步的了解async /await,毕竟目前处理异步函数async /await才是老大。