引言
在ES2017(ES8)標(biāo)準(zhǔn)引入async函數(shù)峰鄙,使得異步操作變得更加方便蜻拨,其實(shí)在ES6中基于Generator+Promise給嵌套的異步任務(wù)提供了一個(gè)非常便捷的解決方案剂公,這是異步任務(wù)非常典型的一種場(chǎng)景。有了生成器的函數(shù)執(zhí)行新模式的出現(xiàn)映砖,讓標(biāo)準(zhǔn)進(jìn)一步跟進(jìn)規(guī)范這一場(chǎng)景的解決方案真慢,這就是async函數(shù)的誕生,而實(shí)際上async函數(shù)就是Generator函數(shù)的語(yǔ)法糖榕吼。
async 是一個(gè)通過(guò)異步執(zhí)行并隱式返回 Promise 作為結(jié)果的函數(shù)(MDN)饿序。
generator
Generator 語(yǔ)法,需要了解function* 羹蚣、yield原探、next三個(gè)基本概念。
generator函數(shù)產(chǎn)生一個(gè)迭代器,迭代器對(duì)外暴露一個(gè)next方法咽弦,通過(guò)這個(gè)方法徒蟆,獲得迭代器的yield的值,同時(shí)獲得done型型,用來(lái)表示下一次next是不是能獲取到值段审。
1.function* 用來(lái)聲明一個(gè)函數(shù)是生成器函數(shù),它比普通的函數(shù)聲明多了一個(gè),的位置比較隨意可以挨著 function 關(guān)鍵字输莺,也可以挨著函數(shù)名
2.yield 產(chǎn)出的意思,這個(gè)關(guān)鍵字只能出現(xiàn)在生成器函數(shù)體內(nèi)裸诽,但是生成器中也可以沒(méi)有yield 關(guān)鍵字嫂用,函數(shù)遇到 yield 的時(shí)候會(huì)暫停,并把 yield 后面的表達(dá)式結(jié)果拋出去
3.next作用是將代碼的控制權(quán)交還給生成器函數(shù)
生成器的使用
代碼示例
function* fun(n) {
let a = yield n * 2;
let b = yield a + 5;
let c = yield b / 2;
return c;
}
let funGen = fun(12);
let resultObj1 = funGen.next();//{value: 24, done: false}
let resultObj2 = funGen.next(resultObj1.value);//{value: 29, done: false}
let resultObj3 = funGen.next(resultObj2.value);//{value: 14.5, done: false}
let resultObj4 = funGen.next(resultObj3.value);//{value: 14.5, done: true}
console.log(resultObj4.value); //14.5
過(guò)程分析
1.生成器就是在普通函數(shù)名稱(chēng)與function關(guān)鍵字之間的任意位置標(biāo)記一個(gè)“*”表示該函數(shù)是一個(gè)生成器丈冬。
2.生成器執(zhí)行會(huì)返回一個(gè)Generator對(duì)象嘱函,也可以視該對(duì)象為一個(gè)Iterator,因?yàn)樵搶?duì)象同樣可以被迭代埂蕊。
3.生成器執(zhí)行生成一個(gè)Generator對(duì)象的時(shí)候迭代器內(nèi)部代碼不會(huì)執(zhí)行往弓,而是需要通過(guò)對(duì)象調(diào)用next()方法才會(huì)執(zhí)行內(nèi)部代碼。
4.Generator對(duì)象調(diào)用next()方法基于yield關(guān)鍵字迭代執(zhí)行蓄氧,next()方法第一次執(zhí)行是從頭開(kāi)始執(zhí)行到第一個(gè)yield的右側(cè)代碼函似,yield左側(cè)代碼會(huì)等到下一個(gè)next()調(diào)用才會(huì)執(zhí)行。當(dāng)所有yield關(guān)鍵被迭代完成以后喉童,最后一個(gè)next()方法返回的對(duì)象中done屬性值為true撇寞,表示該Generator被迭代到最末尾處。
5.被yield截?cái)嗟谋磉_(dá)式除了作為阻斷代碼執(zhí)行的作用以外堂氯,yield關(guān)鍵字同時(shí)充當(dāng)了表達(dá)式右側(cè)代碼的return功能蔑担,將右側(cè)代碼執(zhí)行結(jié)果作為當(dāng)前next()方法的返回值;yield關(guān)鍵還充當(dāng)了左側(cè)代碼被next()調(diào)用時(shí)接收參數(shù)的功能咽白。(yield關(guān)鍵之應(yīng)該很容易理解啤握,它的功能就是截?cái)喑绦驁?zhí)行,并且通過(guò)返回值和接收參數(shù)的方式連接被截?cái)嗟某绦颍?/p>
6.yield作用的return特性其返回值最后會(huì)被next()方法返回的對(duì)象中的value屬性獲取晶框。
生成器 + Promise
使用生成器取代Promise的鏈?zhǔn)秸{(diào)用
let fs = require('fs');
function readFile(path){
return new Promise((res,rej) => {
fs.readFile(path, 'utf-8', (err,data) => {
if(data){
res(data);
}else{
rej(err);
}
});
});
}
function *read(){
let val1 = yield readFile('./data/number.txt');
let val2 = yield readFile(val1);
let val3 = yield readFile(val2);
return val3;
};
let oG = read();
let {value, done} = oG.next();
value.then((val) => {
let {value,done} = oG.next(val);
value.then((val) => {
let {value,done} = oG.next(val);
value.then((val) => {
console.log(val);
});
});
});
生成器遞歸委托:使用委托模式實(shí)現(xiàn)生成器自動(dòng)迭代排抬,使用遞歸消除next的重復(fù)調(diào)用
let fs = require('fs');
function readFile(path){
return new Promise((res,rej) => {
fs.readFile(path, 'utf-8', (err,data) => {
if(data){
res(data);
}else{
rej(err);
}
});
});
}
function *read(){
let val1 = yield readFile('./data/number.txt');
let val2 = yield readFile(val1);
let val3 = yield readFile(val2);
return val3;
};
function Co(oIt){ //生成器迭代委托
return new Promise((res,rej) =>{
let next = (data) => {
let {value, done} = oIt.next(data);
if(done){
res(value);//當(dāng)?shù)鞯竭_(dá)最末尾時(shí),將生成器的返回值傳遞給Promise的受理回調(diào)執(zhí)行回調(diào)任務(wù)
}else{
value.then((val) => {
next(val);//將上一個(gè)生成器返回值傳遞給下一個(gè)生成器的迭代方法next(這是個(gè)遞歸操作)
},rej);//在生成器迭代過(guò)程中如果發(fā)生異常會(huì)調(diào)用rej處理
}
}
next();//生成器第一次執(zhí)行不需要參數(shù)
});
}
Co(read()).then((val) => {
console.log(val);
},(val) => {
console.log(val);
});
async+await
使用async函數(shù)如何改寫(xiě)上節(jié)中的示例:
let fs = require('fs');
function readFile(path){
return new Promise((res,rej) => {
fs.readFile(path, 'utf-8', (err,data) => {
if(data){
res(data);
}else{
rej(err);
}
});
});
}
async function read(){
let val1 = await readFile('./data/number.txt');
let val2 = await readFile(val1);
let val3 = await readFile(val2);
return val3;
};
read().then((val) => {
console.log(val); //99
})
從表面上看async將異步鏈?zhǔn)角短兹蝿?wù)完全轉(zhuǎn)化成了按照代碼編寫(xiě)的先后順序的同步執(zhí)行任務(wù)授段,這里所指代的同步任務(wù)是指由async+await控制的內(nèi)部異步任務(wù)畜埋,async函數(shù)本身是一個(gè)異步任務(wù),它執(zhí)行返回的是一個(gè)Promise對(duì)象用來(lái)處理異步鏈?zhǔn)饺蝿?wù)的最后回調(diào)處理畴蒲。
從async本身來(lái)說(shuō)就是將內(nèi)部代碼交給一個(gè)Promise作為excutor函數(shù)悠鞍,然后將return返回值交給回調(diào)函數(shù),Promise通過(guò)then注冊(cè)的回調(diào)任務(wù)作為微任務(wù)(異步)處理。簡(jiǎn)單的說(shuō)async函數(shù)返回一個(gè)Promise對(duì)象咖祭,并將返回值作為回調(diào)任務(wù)的參數(shù)掩宜,這里的底層實(shí)現(xiàn)可以直接參數(shù)Co理解,也可以稱(chēng)為內(nèi)置執(zhí)行器么翰,轉(zhuǎn)碼工具中的函數(shù)名稱(chēng)是asyncReadFile()牺汤。接著來(lái)看await在async函數(shù)內(nèi)的作用:
async函數(shù)的使用
// 函數(shù)聲明
async function foo(){}
// 函數(shù)表達(dá)式
let foo = async function(){}
// 對(duì)象方法
let obj = {async foo(){}};
obj.foo().then(...);
// class的方法
class Storage{
constructor(){
this.cachePromise = caches.open('avatars');
}
async getAvatar(name){
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jake').then(...);
//箭頭函數(shù)
const foo = async () => {};
參考資料
https://www.cnblogs.com/ZheOneAndOnly/p/11411971.html
https://blog.csdn.net/zjscy666/article/details/95365911?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
http://www.reibang.com/p/6055bd421ca4