async await 實現(xiàn)原理 generator + yield + promise

generator函數(shù)

  • Generator 函數(shù)整體就是一個狀態(tài)機(jī),內(nèi)部有多少個yield,就有多少個狀態(tài)悠瞬,當(dāng)Generator函數(shù)執(zhí)行的時候 其實是返回包含每次代碼暫停執(zhí)行的指針對象(遍歷器對象Iterator)谜洽,可以遍歷 Generator的各種狀態(tài)(Iterator.next 去移動指針可以使 Generator 函數(shù)分段執(zhí)行)

  • {value:xxx,done:false || true},表示這個狀態(tài)的返回值和一個表示這個遍歷對象是否已經(jīng)遍歷到最后(狀態(tài)機(jī)是否走完了所有的狀態(tài))

Iterator接口

typescript的接口描述如下:遍歷器接口(Iterable)、指針對象(Iterator)和next方法返回值

interface Iterable {
  [Symbol.iterator]() : Iterator,
}

interface Iterator {
  next(value?: any) : IterationResult,
}

interface IterationResult {
  value: any,
  done: boolean,
}
  • 一種數(shù)據(jù)結(jié)構(gòu)只要部署了 Iterator 接口檩小,我們就稱這種數(shù)據(jù)結(jié)構(gòu)是“可遍歷的”(iterable)。

  • ES6 規(guī)定烟勋,默認(rèn)的 Iterator 接口部署在數(shù)據(jù)結(jié)構(gòu)的Symbol.iterator屬性规求,或者說筐付,一個數(shù)據(jù)結(jié)構(gòu)只要具有Symbol.iterator屬性,就可以認(rèn)為是“可遍歷的”(iterable)

-ES6 的有些數(shù)據(jù)結(jié)構(gòu)原生具備 Iterator 接口(比如數(shù)組)阻肿,即不用任何處理家妆,就可以被for...of循環(huán)遍歷。原因在于冕茅,這些數(shù)據(jù)結(jié)構(gòu)原生部署了Symbol.iterator屬性(詳見下文)伤极,另外一些數(shù)據(jù)結(jié)構(gòu)沒有(比如對象)。凡是部署了Symbol.iterator屬性的數(shù)據(jù)結(jié)構(gòu)姨伤,就稱為部署了遍歷器接口哨坪。調(diào)用這個接口,就會返回一個遍歷器對象

調(diào)用 Iterator 接口的場合

  • (1)解構(gòu)賦值 對數(shù)組和 Set 結(jié)構(gòu)進(jìn)行解構(gòu)賦值時乍楚,會默認(rèn)調(diào)用Symbol.iterator方法当编。
  • (2)擴(kuò)展運(yùn)算符 擴(kuò)展運(yùn)算符(...)也會調(diào)用默認(rèn)的 Iterator 接口。
  • (3)yield* yield*后面跟的是一個可遍歷的結(jié)構(gòu)徒溪,它會調(diào)用該結(jié)構(gòu)的遍歷器接口忿偷。
let generator = function* () {
  yield 1;
  yield* [2,3,4];
  yield 5;
};

var iterator = generator();

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

for...in循環(huán)有幾個缺點。

數(shù)組的鍵名是數(shù)字臊泌,但是for...in循環(huán)是以字符串作為鍵名“0”鲤桥、“1”、“2”等等渠概。
for...in循環(huán)不僅遍歷數(shù)字鍵名茶凳,還會遍歷手動添加的其他鍵,甚至包括原型鏈上的鍵播揪。
某些情況下贮喧,for...in循環(huán)會以任意順序遍歷鍵名。
總之猪狈,for...in循環(huán)主要是為遍歷對象而設(shè)計的箱沦,不適用于遍歷數(shù)組。
看了好幾遍阮一峰的es6語法才看懂
Generator 函數(shù)返回一個遍歷器 這個遍歷器提供 next() 方法來控制Generator函數(shù)的執(zhí)行雇庙,暫停 谓形。
yield 表達(dá)式 暫停的標(biāo)志

簡易版本的Generator函數(shù)遍歷器自執(zhí)行函數(shù):

// 協(xié)程 callback版本
var timeout = (time)=>{
  return (callbck)=>{
       setTimeout(()=>{
      console.log('time',time)
      callbck()
    },time)
  }
}



// Generator 遍歷器生成函數(shù)
function* callbackGenerator() {
  var res = yield timeout(2000) // 使得 yield 的value 是一個callback 自執(zhí)行遞歸 使下一次的next在callback里面執(zhí)行
  var res1 = yield timeout(2000)
}



// callback版本  版本自執(zhí)行函數(shù)
function run (Generator) {
  var hw = Generator();
 const  next = ()=>{
   const aa = hw.next()
   if(!aa.done){
    aa.value(next) 
   }else{
     return true
   }
 }
 next()
}



run(callbackGenerator)

// 協(xié)程 promise 版本
var timeout = (time)=>{
  return new Promise((resolve)=>{
       setTimeout(()=>{
      console.log('time',time)
      resolve(time+2000)
    },time)
  })
}



// Generator 遍歷起器生成函數(shù)
function* callbackGenerator() {
  var res = yield timeout(2000) // 使得 yield 的value是promise 自執(zhí)行時把下一次的next在then里面執(zhí)行
  var res1 = yield timeout(res)
}



// promise版本 版本自執(zhí)行函數(shù)
function run (Generator) {
  var hw = Generator();
 const  next = (query)=>{
   var aa = hw.next(query)
   if(!aa.done){
    aa.value.then(next) 
   }else{
     return true
   }
 }
 next()
}



run(callbackGenerator)


核心思想:利用generator遍歷器生成器函數(shù)的分段執(zhí)行 ,只有在遍歷器對象 執(zhí)行next方法之后交出了控制權(quán) 状共,在完成后 callback || promise.then()里面調(diào)用下一次next的時候又繼續(xù)恢復(fù)控制權(quán)這個功能來實現(xiàn)的

async await 正式利用了這一點

  • yield 表達(dá)式交出控制權(quán)

兩種方法可以做到這一點套耕。
(1)回調(diào)函數(shù)谁帕。將異步操作包裝成 Thunk 函數(shù)峡继,在回調(diào)函數(shù)里面交回執(zhí)行權(quán)。

(2)Promise 對象匈挖。將異步操作包裝成 Promise 對象碾牌,用then方法交回執(zhí)行權(quán)康愤。
from http://es6.ruanyifeng.com/#docs/generator-async

  • 使generator遍歷器函數(shù)自執(zhí)行

es7 async await 異步寫法

function sleep(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms);
  });
}

async function test() {
  for (let i = 0; i < 10; i++) {
    await sleep(100);
  }
}

轉(zhuǎn)化成es6是下邊這樣
async await 實際上是由 generator + yield 控制流程 + promise 實現(xiàn)回調(diào)

// _asyncToGenerator 顧名思義轉(zhuǎn)換async to Generator

  • 轉(zhuǎn)換 await 為 yield
  • 轉(zhuǎn)換 async 為 Generator
  • 調(diào)用test之前 _asyncToGenerator函數(shù) 已經(jīng)執(zhí)行,并傳遞了一個fn (Generator)給匿名的執(zhí)行函數(shù)
  • 實際上 調(diào)用test之前, test方法已經(jīng)變成了一個 實現(xiàn) Generator 遍歷器生成函數(shù)自執(zhí)行的 方法
  • 調(diào)用test 遞歸執(zhí)行step
    • 調(diào)用next的來執(zhí)行遍歷器
    • 每次執(zhí)行返回一個promise
    • promise的then方法里執(zhí)行next方法)
  • 使得Generator 遍歷器自動 執(zhí)行舶吗, 從而達(dá)到異步
function _asyncToGenerator(fn) {
  return function () {
    var gen = fn.apply(this, arguments);
    return new Promise(function (resolve, reject) {
      function step(key, arg) {
        try {
          var info = gen[key](arg);
          var value = info.value;
        } catch (error) {
          reject(error);
          return;
        }
        if (info.done) {
          resolve(value);
        } else {
          return Promise.resolve(value).then(function (value) {
            return step("next", value);
          },
            function (err) {
              return step("throw", err);
            });
        }
      }
      return step("next");
    });
  };
}

function sleep(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms);
  });
}

let test = function () {
  var ref = _asyncToGenerator(function* () {
    for (let i = 0; i < 10; i++) {
      yield sleep(100);
    }
  });

  return function test() {
    return ref.apply(this, arguments);
  };
}();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末征冷,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子誓琼,更是在濱河造成了極大的恐慌检激,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腹侣,死亡現(xiàn)場離奇詭異叔收,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)傲隶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門饺律,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人跺株,你說我怎么就攤上這事复濒。” “怎么了乒省?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵巧颈,是天一觀的道長。 經(jīng)常有香客問我袖扛,道長洛二,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任攻锰,我火速辦了婚禮晾嘶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘娶吞。我一直安慰自己垒迂,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布妒蛇。 她就那樣靜靜地躺著机断,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绣夺。 梳的紋絲不亂的頭發(fā)上吏奸,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機(jī)與錄音陶耍,去河邊找鬼奋蔚。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的泊碑。 我是一名探鬼主播坤按,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼馒过!你這毒婦竟也來了臭脓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤腹忽,失蹤者是張志新(化名)和其女友劉穎来累,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窘奏,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡佃扼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蔼夜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兼耀。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖求冷,靈堂內(nèi)的尸體忽然破棺而出瘤运,到底是詐尸還是另有隱情,我是刑警寧澤匠题,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布拯坟,位于F島的核電站,受9級特大地震影響韭山,放射性物質(zhì)發(fā)生泄漏郁季。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一钱磅、第九天 我趴在偏房一處隱蔽的房頂上張望梦裂。 院中可真熱鬧,春花似錦盖淡、人聲如沸年柠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冗恨。三九已至,卻和暖如春味赃,著一層夾襖步出監(jiān)牢的瞬間掀抹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工心俗, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留傲武,地道東北人。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像谱轨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子吠谢,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,514評論 2 348