async/await 原理及簡單實現(xiàn)

解決函數(shù)回調經(jīng)歷了幾個階段救崔, Promise 對象锭部, Generator 函數(shù)到async函數(shù)。async函數(shù)目前是解決函數(shù)回調的最佳方案杖爽。很多語言目前都實現(xiàn)了async敲董,包括Python 紫皇,java spring慰安,go等。

async await 的用法

async 函數(shù)返回一個 Promise 對象聪铺,當函數(shù)執(zhí)行的時候化焕,一旦遇到 await 就會先返回,等到觸發(fā)的異步操作完成铃剔,再接著執(zhí)行函數(shù)體內(nèi)后面的語句撒桨。

function getNum(num){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(num+1)
        }, 1000)
    })
}
const func = async ()=>{
    const f1 = await getNum(1)
  const f2 = await getNum(f1)
  console.log(f2) 
  // 輸出3 
}
func()

async /await 需要在function外部書寫async,在內(nèi)部需要等待執(zhí)行的函數(shù)前書寫await即可

深入理解

理解async函數(shù)需要先理解Generator函數(shù)键兜,因為async函數(shù)是Generator函數(shù)的語法糖凤类。

Generator[?d??n??ret?]函數(shù)-生成器

Generator是ES6標準引入的新的數(shù)據(jù)類型。Generator可以理解為一個狀態(tài)機普气,內(nèi)部封裝了很多狀態(tài)谜疤,同時返回一個迭代器Iterator對象∠志鳎可以通過這個迭代器遍歷相關的值及狀態(tài)夷磕。
Generator的顯著特點是可以多次返回,每次的返回值作為迭代器的一部分保存下來仔沿,可以被我們顯式調用坐桩。

Generator函數(shù)的聲明

一般的函數(shù)使用function聲明,return作為回調(沒有遇到return封锉,在結尾調用return undefined)绵跷,只可以回調一次膘螟。而Generator函數(shù)使用function*定義,除了return碾局,還使用yield返回多次萍鲸。

function* foo(x) {
    yield x + 1;
    yield x + 2;
    return x + 3;
}
const result = foo(0) // foo {<suspended>}
result.next();  // {value: 1, done: false}
result.next();  // {value: 2, done: false}
result.next();  // {value: 3, done: true}
result.next();  //{value: undefined, done: true}

在chrome瀏覽器中這個例子里,我們可以看到擦俐,在執(zhí)行foo函數(shù)后返回了一個
Generator函數(shù)的實例脊阴。它具有狀態(tài)值suspended和closed,suspended代表暫停蚯瞧,closed則為結束嘿期。但是這個狀態(tài)是無法捕獲的,我們只能通過Generator函數(shù)的提供的方法獲取當前的狀態(tài)埋合。
在執(zhí)行next方法后备徐,順序執(zhí)行了yield的返回值。返回值有value和done兩個狀態(tài)甚颂。value為返回值蜜猾,可以是任意類型。done的狀態(tài)為false和true振诬,true即為執(zhí)行完畢蹭睡。在執(zhí)行完畢后再次調用返回{value: undefined, done: true}
注意:在遇到return的時候,所有剩下的yield不再執(zhí)行赶么,直接返回{ value: undefined, done: true }

Generator函數(shù)的方法

Generator函數(shù)提供了3個方法肩豁,next/return/throw

next方式是按步執(zhí)行,每次返回一個值,同時也可以每次傳入新的值作為計算

function* foo(x) {
    let a = yield x + 1;
    let b= yield a + 2;
    return x + 3;
}
const result = foo(0) // foo {<suspended>}
result.next(1);  // {value: 1, done: false}
result.next(2);  // {value: 2, done: false}
result.next(3);  // {value: 3, done: true}
result.next(4);  //{value: undefined, done: true}

return則直接跳過所有步驟辫呻,直接返回 {value: undefined, done: true}

throw則根據(jù)函數(shù)中書寫try catch返回catch中的內(nèi)容清钥,如果沒有寫try,則直接拋出異常

function* foo(x) {
  try{
    yield x+1
    yield x+2
    yield x+3
    yield x+4
    
  }catch(e){
    console.log('catch it')
  }
}
const result = foo(0) // foo {<suspended>}
result.next();  // {value: 1, done: false}
result.next();  // {value: 2, done: false}
result.throw();  // catch it {value: undefined, done: true}
result.next();  //{value: undefined, done: true}

這里可以看到在執(zhí)行throw之前放闺,順序的執(zhí)行了狀態(tài)祟昭,但是在遇到throw的時候,則直接走進catche并改變了狀態(tài)怖侦。
這里還要注意一下篡悟,因為狀態(tài)機是根據(jù)執(zhí)行狀態(tài)的步驟而執(zhí)行,所以如果執(zhí)行thow的時候础钠,沒有遇到try catch則會直接拋錯
以下面兩個為例

function* foo(x) {
    yield x+1
  try{
    yield x+2
   }catch(e){
    console.log('catch it')
  }
}
const result = foo(0) // foo {<suspended>}
result.next();  // {value: 1, done: false}
result.next();  // {value: 2, done: false}
result.throw();  // catch it {value: undefined, done: true}
result.next();  //{value: undefined, done: true}

這個例子與之前的執(zhí)行狀態(tài)一樣恰力,因為在執(zhí)行到throw的時候,已經(jīng)執(zhí)行到try語句旗吁,所以可以執(zhí)行踩萎,而下面的例子則不一樣

function* foo(x) {
    yield x+1
  try{
    yield x+2
   }catch(e){
    console.log('catch it')
  }
}
const result = foo(0) // foo {<suspended>}
result.next();  // {value: 1, done: false}
result.throw();  // Uncaught undefined
result.next();  //{value: undefined, done: true}

執(zhí)行throw的時候,還沒有進入到try語句很钓,所以直接拋錯香府,拋出undefined為throw未傳參數(shù)董栽,如果傳入?yún)?shù)則顯示為傳入的參數(shù)。此狀態(tài)與未寫try的拋錯狀態(tài)一致企孩。

遍歷

Generator函數(shù)的返回值是一個帶有狀態(tài)的Generator實例锭碳。它可以被for of 調用,進行遍歷勿璃,且只可被for of 調用擒抛。此時將返回他的所有狀態(tài)

function* foo(x) {
console.log('start')
    yield x+1
   console.log('state 1')
    yield x+2
   console.log('end')
}
const result = foo(0) // foo {<suspended>}
for(let i of result){
    console.log(i)
}
//start
//1
//state 1
//2
//end
result.next() //{value: undefined, done: true}

調用for of方法后,在后臺調用next(),當done屬性為true的時候补疑,循環(huán)退出歧沪。因此Generator函數(shù)的實例將順序執(zhí)行一遍,再次調用時莲组,狀態(tài)為已完成

狀態(tài)的存儲和改變
Generator函數(shù)中yield返回的值是可以被變量存儲和改變的诊胞。

function* foo(x) {
    let a = yield x + 0;
    let b= yield a + 2;
    yield x;
    yield a 
    yield b
}
const result = foo(0)
result.next() //  {value: 0, done: false}
result.next(2) // {value: 4, done: false}
result.next(3) // {value: 0, done: false}
result.next(4) // {value: 2, done: false}
result.next(5) // {value: 3, done: false}

以上的執(zhí)行結果中,我們可以看到锹杈,在第二步的時候撵孤,我們傳入2這個參數(shù),foo函數(shù)中的a的變量的值0被替換為2竭望,并且在第4次迭代的時候邪码,返回的是2。而第三次迭代的時候市框,傳入的3參數(shù)霞扬,替換了b的值4糕韧,并在第5次迭代的時候返回了3枫振。所以傳入的參數(shù),是替代上一次迭代的生成值萤彩。

yield 委托*

在Generator函數(shù)中粪滤,我們有時需要將多個迭代器的值合在一起,我們可以使用yield *的形式雀扶,將執(zhí)行委托給另外一個Generator函數(shù)

function* foo1() {
    yield 1;
    yield 2;
    return "foo1 end";
}

function* foo2() {
    yield 3;
    yield 4;
}

function* foo() {
    yield* foo1();
    yield* foo2();
      yield 5;
}

const result = foo();

console.log(iterator.next());// "{ value: 1, done: false }"
console.log(iterator.next());// "{ value: 2, done: false }"
console.log(iterator.next());// "{ value: 3, done: false }"
console.log(iterator.next());// "{ value: 4, done: false }"
console.log(iterator.next());// "{ value: 5, done: false }"
console.log(iterator.next());// "{ value: undefined, done: true }"

foo在執(zhí)行的時候杖小,首先委托給了foo1,等foo1執(zhí)行完畢愚墓,再委托給foo2予权。但是我們發(fā)現(xiàn),”foo1 end” 這一句并沒有輸出浪册。
在整個Generator中扫腺,return只能有一次,在委托的時候村象,所有的yield*都是以函數(shù)表達式的形式出現(xiàn)笆环。return的值是表達式的結果攒至,在委托結束之前其內(nèi)部都是暫停的,等待到表達式的結果的時候躁劣,將結果直接返回給foo迫吐。此時foo內(nèi)部沒有接收的變量,所以未打印账忘。
如果我們希望捕獲這個值志膀,可以使用yield *foo()的方式進行獲取。

實現(xiàn)一個簡單的async/await

如上鳖擒,我們掌握了Generator函數(shù)的使用方法梧却。async/await語法糖就是使用Generator函數(shù)+自動執(zhí)行器來運作的。 我們可以參考以下例子

// 定義了一個promise败去,用來模擬異步請求放航,作用是傳入?yún)?shù)++
function getNum(num){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(num+1)
        }, 1000)
    })
}

//自動執(zhí)行器,如果一個Generator函數(shù)沒有執(zhí)行完圆裕,則遞歸調用
function asyncFun(func){
  var gen = func();

  function next(data){
    var result = gen.next(data);
    if (result.done) return result.value;
    result.value.then(function(data){
      next(data);
    });
  }

  next();
}

// 所需要執(zhí)行的Generator函數(shù)广鳍,內(nèi)部的數(shù)據(jù)在執(zhí)行完成一步的promise之后,再調用下一步
var func = function* (){
  var f1 = yield getNum(1);
  var f2 = yield getNum(f1);
  console.log(f2) ;
};
asyncFun(func);

在執(zhí)行的過程中吓妆,判斷一個函數(shù)的promise是否完成赊时,如果已經(jīng)完成,將結果傳入下一個函數(shù)行拢,繼續(xù)重復此步驟祖秒。

總結

async/await非常好理解,基本理解了Generator函數(shù)之后舟奠,幾句話就可以描述清楚竭缝。這里沒有過多的繼續(xù)闡述Generator函數(shù)的內(nèi)部執(zhí)行邏輯及原理,如果有對此有深入理解的童鞋沼瘫,歡迎補充說明抬纸。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市耿戚,隨后出現(xiàn)的幾起案子湿故,更是在濱河造成了極大的恐慌,老刑警劉巖膜蛔,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坛猪,死亡現(xiàn)場離奇詭異,居然都是意外死亡皂股,警方通過查閱死者的電腦和手機墅茉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人躁锁,你說我怎么就攤上這事纷铣。” “怎么了战转?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵搜立,是天一觀的道長。 經(jīng)常有香客問我槐秧,道長啄踊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任刁标,我火速辦了婚禮颠通,結果婚禮上,老公的妹妹穿的比我還像新娘膀懈。我一直安慰自己顿锰,他們只是感情好,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布启搂。 她就那樣靜靜地躺著硼控,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胳赌。 梳的紋絲不亂的頭發(fā)上牢撼,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音疑苫,去河邊找鬼熏版。 笑死,一個胖子當著我的面吹牛捍掺,可吹牛的內(nèi)容都是我干的撼短。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼乡小,長吁一口氣:“原來是場噩夢啊……” “哼阔加!你這毒婦竟也來了?” 一聲冷哼從身側響起满钟,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胳喷,沒想到半個月后湃番,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡吭露,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年吠撮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讲竿。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡泥兰,死狀恐怖弄屡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鞋诗,我是刑警寧澤膀捷,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站削彬,受9級特大地震影響全庸,放射性物質發(fā)生泄漏。R本人自食惡果不足惜融痛,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一壶笼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧雁刷,春花似錦覆劈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至侯勉,卻和暖如春鹦筹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背址貌。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工铐拐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人练对。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓遍蟋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親螟凭。 傳聞我的和親對象是個殘疾皇子虚青,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容