重學JS(五)—— async原理

上一篇 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(){})());
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 異步編程對JavaScript語言太重要。Javascript語言的執(zhí)行環(huán)境是“單線程”的,如果沒有異步編程,根本...
    呼呼哥閱讀 7,399評論 5 22
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持,譯者再次奉上一點點福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠,并抽取幸運大...
    HetfieldJoe閱讀 6,445評論 9 19
  • 你不知道JS:異步 第四章:生成器(Generators) 在第二章,我們明確了采用回調(diào)表示異步流的兩個關(guān)鍵缺點:...
    purple_force閱讀 1,035評論 0 2
  • 弄懂js異步 講異步之前,我們必須掌握一個基礎(chǔ)知識-event-loop。 我們知道JavaScript的一大特點...
    DCbryant閱讀 2,867評論 0 5
  • async 函數(shù) 含義 ES2017 標準引入了 async 函數(shù),使得異步操作變得更加方便。 async 函數(shù)是...
    huilegezai閱讀 1,312評論 0 6

友情鏈接更多精彩內(nèi)容