前言
講真為了寫(xiě)出更優(yōu)雅、更易維護(hù)的代碼,為了解決異步的嵌套問(wèn)題,真是操碎了心,先是出了個(gè)Promise,然后又是Generator、yield組合,直到ES7的async、await組合。好在事情一直在向好的方向反正。
Generator
生成器對(duì)象是由function* 返回的,并且符合可迭代協(xié)議和迭代器協(xié)議。
這里有幾個(gè)概念生成器、可迭代協(xié)議、迭代器協(xié)議。具體的概念可以點(diǎn)擊鏈接查看MDN文檔。
function*: 定義一個(gè)生成器函數(shù),返回一個(gè)Generator對(duì)象;
可迭代協(xié)議: 允許 JavaScript 對(duì)象去定義或定制它們的迭代行為;
迭代器協(xié)議: 定義了一種標(biāo)準(zhǔn)的方式來(lái)產(chǎn)生一個(gè)有限或無(wú)限序列的值;當(dāng)一個(gè)對(duì)象被認(rèn)為是一個(gè)迭代器時(shí),它實(shí)現(xiàn)了一個(gè) next() 的方法,next()返回值如下:
{
done:true,//false迭代是否結(jié)束,
value:v,//迭代器返回值
}
從這幾個(gè)基本的概念我們可以了解到,生成器是對(duì)象是可以迭代的,那么為什么要可以迭代、可以迭代解決了什么問(wèn)題。
迭代
下面定義一個(gè)簡(jiǎn)單的迭代生成函數(shù),傳入一個(gè)數(shù)組,則返回一個(gè)可以迭代的對(duì)象
// 1. 迭代器
let iterator = (items)=>{
let iter = {
index:0,
max:items.length,
next:function(){ // 返回調(diào)用結(jié)果
return this.index === this.max ? {value:undefined,done:true} : {value:items[this.index++],done:false};
}
}
return iter;
}
export default iterator;
調(diào)用上面的迭代器,并執(zhí)行
let iter = iterator([1,2,3,4]);
let result = null;
console.log('``````iterator````````');
do{
result = iter.next();
console.log(result);
}while (!result.done)
運(yùn)行結(jié)果如下:
可以看到,迭代器每次調(diào)用next()方法,都會(huì)返回{value:xx,done:xx}結(jié)構(gòu)的對(duì)象,這個(gè)就是迭代器協(xié)議中next()方法需要遵循的規(guī)則,前面說(shuō)過(guò)generator函數(shù)也是遵循迭代器協(xié)議的,下面用generator實(shí)現(xiàn)此功能。
generator的使用
// generator
function *generator(items){
let index = 0;
let max = items.length;
while (index < max){
yield items[index++];
}
}
let gene = generator([1,2,3,4]);
result = null;
console.log('``````````generator`````````');
do{
result = gene.next();
console.log(result)
}while(!result.done)
此時(shí)運(yùn)行結(jié)果如下:
對(duì)比兩次運(yùn)行的結(jié)果,得出一個(gè)結(jié)論:生成器(function*)函數(shù),運(yùn)行時(shí),返回的是一個(gè)生成器對(duì)象,這個(gè)生成器對(duì)象是可以迭代(gene.next())的,并且next()的返回值包含value,done兩個(gè)字段。
進(jìn)化
生成器是可以迭代的,而且返回值也是符合一定結(jié)構(gòu)的,我們每次再使用生成器的時(shí)候,都要用循環(huán)去執(zhí)行,知道返回的done為true,為了簡(jiǎn)化操作需要把這個(gè)循環(huán)操作進(jìn)行封裝,下面封裝一個(gè)簡(jiǎn)單的run函數(shù),run可以執(zhí)行迭代器,一直到完成任務(wù)
let tick = (duration)=>{
return new Promise((resolve)=>{
setTimeout(function () {
console.log(duration,new Date());
resolve(duration);
},duration);
});
};
function *generator() {
var result = yield tick(2000);
console.log('result = ',result);
result = yield tick(4000);
console.log('result = ',result);
result = yield tick(3000);
console.log('result = ',result);
}
let run = (generator,res)=>{
var result = generator.next(res);
if(result.done) return;
result.value.then((res)=>{
run(generator,res);
});
}
run(generator());
以上的運(yùn)行結(jié)果:
看一下run的實(shí)現(xiàn),像極了前面的do...while... 循環(huán),只是做了一個(gè)簡(jiǎn)單的封裝,以后就沒(méi)用每次都手寫(xiě)循環(huán)來(lái)執(zhí)行生成器函數(shù)了,實(shí)際上有一個(gè)封裝好的庫(kù)可以使用它叫co
co庫(kù)執(zhí)行g(shù)enerator
安裝co
npm install --save co
使用
import co from 'co';
co(generator);
運(yùn)行結(jié)果如下
它的作用跟上面實(shí)現(xiàn)的run方法的作用是一樣的,都是執(zhí)行g(shù)enerator,并返回結(jié)果。這樣生成器大概就可以理解了,說(shuō)白了生成器就是可以返回一個(gè)可迭代的對(duì)象,這個(gè)對(duì)象不是通過(guò)return返回的,而是通過(guò)yield,并且可以實(shí)現(xiàn)異步函數(shù)的同步調(diào)用,我們看上圖的時(shí)間,雖然tick是異步的,但是打印的結(jié)果卻是順序執(zhí)行的。
async/await
generator可以簡(jiǎn)化異步的編碼,減少嵌套,而async、await組合起來(lái)使用,可以更進(jìn)一步,類(lèi)似以上的代碼,使用async、await改寫(xiě)如下
let tick = (duration)=>{
return new Promise((resolve)=>{
setTimeout(function () {
console.log(new Date());
resolve(duration);
},duration);
});
}
async function asyncFunc(){
var result = await tick(1000);
console.log(result);
result = await tick(2000);
console.log(result);
result = await tick(3000);
console.log(result);
}
asyncFunc();
執(zhí)行結(jié)果
雖然實(shí)現(xiàn)的功能是一樣的,但是從代碼的結(jié)構(gòu)上又簡(jiǎn)化了一層。