一、 Generator函数
1. 基本概念及用法
Generator函数是ES6提出的一种新的异步解决方案,它不同于一般的函数。Generator函数前面必须有*,函数体内有yiled标识暂停标记。
我们可以将Generator理解为一个状态机,里面可以存在很多异步的操作。函数执行的结果不是return的值,而是会返回一个遍历器对象,通过执行遍历器对象上的next方法,可以改变函数内部的状态指针,进而遍历Generator函数内部的每一个状态。首先,我们了解一下这个函数的基本写法:
function* gen() {
yield 1; // yield 暂停执行的标记
yield 2;
yield 3;
yield 4;
}
const iterater = gen(); // iterater是一个遍历器,
// 可以通过它的next方法来遍历generator函数的内部状态
iterater.next(); // {value : 1, done : false}
iterater.next(); // {value : 2, done : false}
iterater.next(); // {value : 3, done : false}
iterater.next(); // {value : 4, done : false}
iterater.next(); // {value : undefined, done : true}
遍历器的执行机制:通过调用iterater的next方法,使得generator函数的内部指针移向下一个状态,
指针从函数头部或者上一次暂停的位置开始执行,直到下一个yield或return语句。
大概可以理解为generator函数是分段执行的,遇到yield可以暂停函数的执行,next可以恢复执行。
虽然yield和return后面表达式的值都作为next()函数执行完返回的value属性的值,但它们是有所区别的,函数遇到return会认为遍历结束,遇到yiled只会暂停,还会有下一次的遍历
2. next方法的传参的注意点
next方法的执行是可以传参,而且next方法传入的参数作为上一个yield表达式的返回值
如果next方法执行的时候没有传入参数,那么上一个yiled表达式结果为undefined
第一个next的执行传入的参数是无效的
function* gen() {
var val1 = yield 1;
console.log(val1) // 2
var val2 = yield 2;
console.log(val2) // 3
var val3 = yield val2 + 3;
console.log(val3) // 4
var val4 = yield 4;
console.log(val4) // 5
}
const iterater = gen();
iterater.next(1); //第一个next执行的时候传入的参数是无效的 {value: 1, done: false}
iterater.next(2); //{value: 2, done: false}
iterater.next(3); //{value: 6, done: false}
iterater.next(4); //{value: 4, done: false}
iterater.next(5); //{value: undefined, done: true}
3. Generator函数的使用(生成迭代器)
for...of循环内部其实是调用需要遍历的数据类型上部署的Iterator接口,这个接口其实就是自动调用迭代器的next方法,我们可以通过generator函数尝试给对象添加这个属性,使对象也可以通过for...of遍历
与此同理,扩展运算符...、解构赋值、Array.from内部调用的都是遍历器接口
//通过generator实现对象的迭代器接口
const obj = {
'key1': 1,
'key2': 2,
'key3': 3,
[Symbol.iterator] : function *(){ //这样我们通过generator函数实现了一个迭代器
// 由于对象本身没有下标,也没有顺序,我们可以将对象转换为map
const map = new Map();
map.set('key1',1)
map.set('key2',2)
map.set('key3',3)
console.log(map)
// 0: {"key1" => 1}
// 1: {"key2" => 2}
// 2: {"key3" => 3}
// size: 3
console.log([...map.entries()])
//map.entries()方法返回的对象可迭代
//...运算符其实底层也是通过执行该对象的迭代器方法
const mapResult = [...map.entries()];
var index = 0;
var size = map.size;
while(index < size){
yield mapResult[index++];
}
}
}
// for...of会自动调用迭代器上的next方法
for(let item of obj){
console.log(item) // ["key1", 1] ["key2", 2] ["key3", 3]
}
//! 需要特别注意的是:for...of在next返回对象的done属性为true时就会停止遍历,
//且不会包含done为true时对应的value值
二、执行器
虽然generator函数是一种很好的异步解决方案,可以让我们像写同步代码一样去处理异步逻辑,但是我们可以发现,每次都要手动执行生成迭代器,手动调用next方法来遍历,这种方式处理业务也不太友好,所以我们需要一个执行器自动来帮我们遍历所有的状态。
function readFile(url){
return new Promise(function(resolve,reject){
fs.readFile(url,function(error,data){
if(error) reject(error);
resolve(data)
})
})
}
function* gen(){
var f1 = yield readFile('./a.txt');
console.log(f1)
var f2 = yield readFile('./b.txt');
console.log(f2)
}
function co( generator ){
const iterator = generator();
return new Promise(function(resolve,reject){
// 通过递归的方式遍历内部状态
function diff(value){
ret = iterator.next(value);
if(ret.done) return resolve(ret.value);
Promise.resolve(ret.value).then(function(data){
diff(data.toString())
});
}
try{
diff()
}catch(err){
reject(err)
}
})
}
co(gen)
co模块是自己实现的一个执行器模块,co方法,接收一个generator函数作为参数,
co内部会自动根据构造器函数生成迭代器,并且自动迭代每个状态,拿到异步的结果,
并通过next传参的方式,将每个yiled结果的值赋值给对应yiled执行结果,
最终,我们就在generator函数里打印出来了对应文件的内容
通过执行器执行了generator函数,我们会发现,现在在generator的写法和async函数很类似了,没错,async其实就是generator函数的语法糖,比generator函数多了一个执行器模块
三、 async await
有了上述内容的铺垫,相信大家再来理解async函数就很简单了,它其实就是generator函数自带一个执行器,且比generator函数语义更清晰,可以直接拿到await后面的表达式的结果,不需要手动执行迭代器。
1.async函数的使用
async函数的返回值是一个promise,可以通过.then拿到函数的返回值,如果函数没有return值,那么值就是undefiend.
// async用来标识是async函数
async function getInfo(){
// await 等待异步执行,返回结果后再继续执行
var result1 = await ajax('https://api.github.com/users/github');
console.log(result)
var result2 = await ajax('https://api.github.com/users/github');
console.log(result2)
}
// 函数的调用就和普通的函数类似,只需调用一次,函数体内的多个异步会一次执行
var pro = getInfo()
pro.then(function(data){
console.log(data) //undefined
})
2.async执行后返回的promise状态变化
一个async函数被执行,会立即返回一个Promise对象,只有当await后面所有的promise对象执行完,才会发生变化,除了中途遇到return 语句或者代码执行错误。即函数体内的所有异步操作执行完毕后,才会执行then方法的回调。
3.await
await命令后面,可以是Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作),如果后面不是Promise对象,会自动转化为一个立即resolve的Promise对象
async function asy(){
var res1 = await 3;
}
==> 等同于:
async function asy(){
var res1 = await Promise.resolve(3);
}
await命令后面的Promise如果变为rejected状态,那么async返回的Promise也会变为rejected状态
async function asy(){
await Promise.reject('error')
const f2 = await readFile('./a.txt') //后面的代码不会执行了
}
asy().then(function(data){
console.log(data)
}).catch(function(err){
console.log(err) //error
})
- 因为Promise对象运行的结果很有可能是rejected状态,为了不让一个异步操作的失败,影响后面的异步操作,我们最好把await语句都放在try...catch中
async function asy(){
let f2;
try {
await Promise.reject('error')
} catch (error) {
console.log(error)
}
f2 = await readFile('./a.txt')
return f2
}
asy().then(function(data){
console.log(data) //最后会打印出来f2的结果
}).catch(function(err){
console.log(err)
})
- 多个await后面的异步操作如果没有继发关系,最好是让它们同时触发,这样能够提高执行速度
async function asy(){
// let f1 = await readFile('./a.txt')
// let f2 = await readFile('./b.txt')
let rf1 = readFile('./a.txt')
let rf2 = readFile('./b.txt')
let f1 = await rf1;
let f2 = await rf2;
}
好了,关于async,Generator的这篇文章就分享到这儿了,喜欢的小伙伴们点个赞,不足之处也希望大家在评论中指出,一起学习,一起进步!