上一篇 Promise 講述了用Promise來解決回調(diào)帶來的多嵌套,可讀性差的問題。其中有這段話
最理想的方式當然是這樣
readFile(a,funcA); readFile(b,funcB); readFile(c,funcC); readFile(d,funcD);但是他不能保證讀完A文件后才讀B。
結(jié)果ES6又幫你做到了,你不再需要通過添加回調(diào)來欺騙自己不能按順序書寫的短處?,F(xiàn)在你可以堂堂正正的從上而下寫代碼。
語法
async函數(shù)返回一個 Promise 對象,可以使用then方法添加回調(diào)函數(shù)。當函數(shù)執(zhí)行的時候,一旦遇到await就會先返回,等到異步操作完成,再接著執(zhí)行函數(shù)體內(nèi)后面的語句。
async function print(){
return 1;
}
print().then(e => console.log(e)); //1
async函數(shù)內(nèi)部return語句返回的值,會成為then方法回調(diào)函數(shù)的參數(shù)。
回到上一篇的例子
function readFile(a){
return new Promise(resolve=>{
setTimeout(()=>{
console.log(a);
resolve(a);
},500)
})
}
readFile('a')
.then(()=>{
console.log('b')
})
.then(()=>{
console.log('c')
}); //a b c
現(xiàn)在可以寫成這樣了
async function test(){
await readFile('a');
console.log('b');
console.log('c')
}
test(); //a b c
它依舊會按順序打出a b c。因為async函數(shù)執(zhí)行的時候,遇到await,就會等待readFile函數(shù)先完成,再執(zhí)行后面的代碼。這種寫法更為簡潔,幾乎和同步代碼的寫法一致。語義化很好,就算你沒學過這個語法,看到await,也會覺得函數(shù)會等待readFile返回結(jié)果。
再寫個測試代碼:
async function test(){
await readFile('a');
console.log('b');
console.log('c')
}
test();
console.log('d'); //d a b c
d的打印沒有被await阻塞住,先打印了d。await只會影響async函數(shù)內(nèi)部的執(zhí)行順序。
實現(xiàn)原理
肯定有人會好奇async是怎樣的實現(xiàn)原理,想要理解它,還是得學習生成器(generator)。畢竟async只是generator的語法糖,跳過它直接學習async當然會錯過很多。async 就等于Generator+自動執(zhí)行器。
Generator
var x = 1;
function *foo(){
x++;
yield 'hello';
x++;
console.log(x);
}
var it = foo();
這里并沒有打出理想的3。生成器*foo()沒有像普通函數(shù)一樣運行,它只是創(chuàng)建了一個迭代器。
it.next(); //{value:'hello',done:false}
console.log(x); //2
it.next(); //3 {value:undefined,done:true}
第一次調(diào)用next(),會從函數(shù)開始位置執(zhí)行到第一個暫停點(yield處)。它返回了一個對象,對象的value值就是當前yield的值。此時執(zhí)行了一次x++;所以x的值為2。再次執(zhí)行next()會運行到函數(shù)結(jié)束。這種方式可以讓函數(shù)體的代碼分段執(zhí)行。而不再是一調(diào)用函數(shù)就自動幫你從到尾執(zhí)行完,需要你手動next()來控制代碼的進度。所以用它來處理異步的方式也比較明了了。yield暫停函數(shù)體代碼,當異步操作完成后再使用next()恢復函數(shù)的代碼。
function readFile(a){
return new Promise(resolve=>{
setTimeout(()=>{
console.log(a);
resolve(a);
},500)
})
}
function *foo(){
var result = yield readFile('a');
console.log('b');
}
var it = foo();
var result = it.next(); //next返回的value是readFile函數(shù)返回的Promise對象
result.value.then(()=>{ //給Promise對象增加成功的回調(diào)
it.next(); //當Promise成功后恢復foo()函數(shù)執(zhí)行
}); //a b
按順序打出了a和b。很好理解,執(zhí)行到readFile的時候暫停了foo函數(shù),直到Promise被解決后調(diào)用了next(),才恢復了函數(shù).。雖然foo()函數(shù)內(nèi)部好看了,但是想要控制foo函數(shù)的暫停和繼續(xù)卻需要寫額外的代碼。所以如果有人能幫忙控制就最好不過了,不然我寧愿用Promise。
自動執(zhí)行器
瀏覽器可不會幫你管理生成器,你得自己寫??梢钥吹?,it.next()返回{value:x,done:false}。根據(jù)done的值是可以知道生成器內(nèi)部代碼是否執(zhí)行完成。那根據(jù)他寫個遞歸出來不就美滋滋了。試試。
function run(g){
var res = g.next();
if(!res.done){
run(g);
}
}
不對,異步操作還沒返回結(jié)果,就被你繼續(xù)執(zhí)行了。還得改改
function run(g){
var res = g.next(); //記住res.value是個promise對象
if(!res.done){
res.value.then(()=>{ //promise解決了才調(diào)用next()繼續(xù)執(zhí)行生成器內(nèi)部函數(shù)
run(g);
})
}
}
測試一波
function readFile(a){
return new Promise(resolve=>{
setTimeout(()=>{
console.log(a);
resolve(a);
},500)
})
}
function *foo(){
console.log('a');
var result = yield readFile('b');
console.log('c');
}
function run(g){
var res = g.next(); //記住res.value是個promise對象
if(!res.done){
res.value.then(()=>{ //promise解決了才繼續(xù)執(zhí)行生成器內(nèi)部函數(shù)
run(g);
})
}
}
run(foo());
console.log('d');
打出了a d b c。完美!第一次執(zhí)行foo().next()會打印出a。然后生成器內(nèi)函數(shù)暫停。在打出d。定時器到了后再打出b和c。這個簡單的自動執(zhí)行器,是針對yield后面跟著promise對象的情況。實際使用可能不能這么寫,它只是幫助你理解。實戰(zhàn)可以選擇co模塊。
async 等于Generator+自動執(zhí)行器,現(xiàn)在應(yīng)該很好理解了。
async function test(){};test();
//等價于
run((function *test(){})());