異步的解決方案

回調(diào)

回調(diào)函數(shù)就是一個(gè)通過(guò)函數(shù)指針調(diào)用的函數(shù)聚至。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個(gè)函數(shù)酷勺,當(dāng)這個(gè)指針被用來(lái)調(diào)用其所指向的函數(shù)時(shí),我們就說(shuō)這是回調(diào)函數(shù)扳躬〈嗨撸回調(diào)函數(shù)不是由該函數(shù)的實(shí)現(xiàn)方直接調(diào)用,而是在特定的事件或條件發(fā)生時(shí)由另外的一方調(diào)用的贷币,用于對(duì)該事件或條件進(jìn)行響應(yīng)击胜。
簡(jiǎn)單的說(shuō)回調(diào)函數(shù)是參數(shù)傳遞函數(shù),并指定它在響應(yīng)某個(gè)事件(定時(shí)器片择、鼠標(biāo)點(diǎn) 擊潜的、Ajax 響應(yīng)等)時(shí)執(zhí)行這個(gè)參數(shù)傳遞的函數(shù)。這個(gè)參數(shù)就是回調(diào)字管玻回頭調(diào)用。回調(diào)傳參

假定有兩個(gè)函數(shù)f1和f2嘲叔,后者等待前者的執(zhí)行結(jié)果亡呵。
如果f1是一個(gè)很耗時(shí)的任務(wù),可以考慮改寫(xiě)f1硫戈,把f2寫(xiě)成f1的回調(diào)函數(shù)锰什。

    function f1(callback){
    setTimeout(function () {
      // f1的任務(wù)代碼
      callback(); // 這是回調(diào)   因?yàn)槭莝etTimeout計(jì)時(shí)后觸發(fā)的。
    }, 1000);
  }
    function f1(callback){
   callback(); // 這不是回調(diào)   因?yàn)槭莄allback()自己直接調(diào)用的。
  }

執(zhí)行代碼就變成下面這樣:f1(f2);
回調(diào)會(huì)造成的問(wèn)題:

  • 層次的嵌套汁胆,俗稱(chēng)回調(diào)金字塔

還有什么問(wèn)題嗎梭姓??jī)H僅是代碼看起來(lái)混亂嗎?

順序的大腦
//偽代碼
doA(function(){
  doB();
  doC(function() {
    doD();
  });
  doE();
});
doF();

無(wú)論多么熟悉JS異步的人嫩码,要完全搞懂這段代碼實(shí)際的運(yùn)行順序誉尖,恐怕也得思考一番。
為什么會(huì)這樣铸题? → 因?yàn)槿说拇竽X是順序的铡恕,天生適合順序的思考,難以理解(不是不能理解)非順序的東西丢间。
無(wú)論是在書(shū)寫(xiě)還是在閱讀這段代碼的時(shí)候探熔,我們的大腦都會(huì)下意識(shí)地以為這段代碼的執(zhí)行邏輯是這樣的doA→doB→doC→doD→doE→doF,然而實(shí)際運(yùn)行邏輯很可能(假設(shè))是這樣的doA→doF→doB→doC→doE→doD烘挫。

思想實(shí)驗(yàn)

我們來(lái)進(jìn)行兩種游戲

  1. 第一種游戲诀艰,舉辦方提前跟你說(shuō):“這游戲總共有X關(guān)。第一關(guān)你應(yīng)該做.....然后在....(地方)進(jìn)入第二關(guān)墙牌。第二關(guān)你應(yīng)該做....然后在....(地方)進(jìn)入第三關(guān)涡驮。……"喜滨。我稱(chēng)之為呆板的游戲捉捅。
  2. 第二種游戲,舉辦方提前跟你說(shuō):”你只管從這個(gè)門(mén)口進(jìn)去虽风,等你快到下一關(guān)的時(shí)候棒口,自然會(huì)有人出來(lái)給你提示」枷ィ“我稱(chēng)之為靈活的游戲无牵。
    對(duì)應(yīng)到代碼當(dāng)中,我們便能發(fā)現(xiàn)回調(diào)的另一個(gè)嚴(yán)重問(wèn)題:硬編碼厂抖。
    前后的操作被回調(diào)強(qiáng)制硬編碼綁定到一起了茎毁。在調(diào)用函數(shù)A的時(shí)候,你必須指定A結(jié)束之后該干什么忱辅,并且顯式地傳遞進(jìn)去七蜘。這樣,其實(shí)你已經(jīng)指定了所有的可能事件和路徑墙懂,代碼將變得僵硬且難以維護(hù)橡卤。同時(shí),在閱讀代碼的時(shí)候损搬,由于必須記住前后的關(guān)聯(lián)操作碧库,這也加重了大腦記憶的負(fù)擔(dān)
    柜与。

Promise

Promise 是 ES 2015 原生支持的,他把原來(lái)嵌套的回調(diào)改為了級(jí)聯(lián)的方式嵌灰。

var a = new Promise(function(resolve, reject) {
  setTimeout(function() {
      resolve('1')
  }, 2000)
})
a.then(function(val) {
    console.log(val)
})

如果要涉及到多個(gè)異步操作的順序執(zhí)行問(wèn)題弄匕,我們可以這樣寫(xiě):

var a = new Promise(function(resolve, reject) {
  setTimeout(function() {
      resolve('1')
  }, 2000)
})

a
  .then(function(val){
    console.log(val)
    return new Promise(function(resolve, reject) { // 必須加return不然promise不止到這個(gè)then函數(shù)中又是個(gè)promise。只有通過(guò)返回值判斷一下伞鲫。
      setTimeout(function() {
          resolve('2')
      }, 2000)
    })
  })
  .then(function(val) {
    console.log(val)
  })

以Ajax為例粘茄。我們都知道,在Ajax執(zhí)行成功之后秕脓,指定的回調(diào)函數(shù)會(huì)被放入”任務(wù)隊(duì)列“中。JS執(zhí)行引擎在主線程空閑的時(shí)候便會(huì)輪詢(xún)?nèi)蝿?wù)隊(duì)列儒搭,執(zhí)行其中的任務(wù)吠架。
我們仔細(xì)想想,是不是漏了一個(gè)關(guān)鍵點(diǎn):”我知道最終是JS引擎執(zhí)行了這個(gè)回調(diào)函數(shù)搂鲫。但是傍药,到底是誰(shuí)調(diào)度這個(gè)回調(diào)函數(shù)的?到底是誰(shuí)在特定的時(shí)間點(diǎn)把這個(gè)回調(diào)函數(shù)放入任務(wù)隊(duì)列中去魂仍?“
答案是宿主環(huán)境拐辽,在本例中也就是瀏覽器。是瀏覽器檢測(cè)到Ajax已經(jīng)成功返回擦酌,是瀏覽器主動(dòng)將指定的回調(diào)函數(shù)放到”任務(wù)隊(duì)列”中俱诸,JS引擎只不過(guò)是執(zhí)行而已。
由此赊舶,我們澄清了一件(可能令人震驚)的事情: 在回調(diào)時(shí)代睁搭,盡管你已經(jīng)能夠編寫(xiě)異步代碼了。但是笼平,其實(shí)JS本身园骆,從來(lái)沒(méi)有真正內(nèi)建直接的異步概念,直到ES6的出現(xiàn)寓调。
事實(shí)就是如此锌唾。JS引擎本身所做的只不過(guò)是在不斷輪詢(xún)?nèi)蝿?wù)隊(duì)列,然后執(zhí)行其中的任務(wù)夺英。JS引擎根本不能做到自己主動(dòng)把任務(wù)放到任務(wù)隊(duì)列中晌涕,任務(wù)的調(diào)度從來(lái)都是宿主完成的。舉個(gè)形象的例子就是:“JS引擎就像是流水線上的工人秋麸,宿主就像是派活的老板渐排。工人只知道不斷地干活异雁,不斷地完成流水線上出現(xiàn)的任務(wù)锁蠕,這些任務(wù)都是老板給工人指定的将饺。工人從來(lái)沒(méi)有(也不能)自己給自己派活,自己給自己的流水線上放任務(wù)蹋半。”
ES6從本質(zhì)上改變了在哪里管理事件循環(huán)弃甥,這意味著在技術(shù)上將其納入了JavaScript引擎的勢(shì)力范圍惶室,而不再是由宿主來(lái)管理。
為什么說(shuō)ES6的Promise才真正有了異步呢帘靡?因?yàn)镻romise是基于任務(wù)隊(duì)列(job queue)知给。可以js方面的在事件循環(huán)中插隊(duì)描姚。并不是宿主環(huán)境控制的涩赢。
Promise 是一個(gè) Job,所以必然異步的轩勘,因?yàn)?then 總是返回 Promise筒扒,xxx.then(a => a) 的效果實(shí)際上是 return new Promise(resolve => resolve(a)),所以then也是異步绊寻。

es6引入了Generator

為什么會(huì)出現(xiàn)Generator

本質(zhì)上花墩,generator是一個(gè)函數(shù),它執(zhí)行的結(jié)果是一個(gè)iterator迭代器澄步,每一次調(diào)用迭代器的next方法冰蘑,就會(huì)產(chǎn)生一個(gè)新的值。迭代器本身就是用來(lái)生成一系列值的村缸,同時(shí)也廣泛應(yīng)用于擴(kuò)展運(yùn)算符...祠肥、解構(gòu)賦值和for...of循環(huán)遍歷等地方。

generator函數(shù)的函數(shù)是分段的王凑。第一次執(zhí)行next的時(shí)候搪柑,程序會(huì)執(zhí)行到第一個(gè)yield,然后返回{ value:1, done:false }索烹,表示yield后面返回1工碾,但是函數(shù)Hello還沒(méi)執(zhí)行完,函數(shù)既不會(huì)退出百姓,也不會(huì)往下執(zhí)行渊额。

function p(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(new Date());
    }, time)
  });
}

function* delay(){
  let time1 = yield p(1000);
  console.log(1, time1);

  let time2 = yield p(1000);
  console.log(2, time2)

  let time3 = yield p(2000);
  console.log(3, time3);
}

function co(gen){
  let it = gen();
  next();
  function next(arg){
    let ret = it.next(arg);
    if(ret.done) return;
    ret.value.then((data) => {
      next(data)
    })
  }
}

co(delay);// 這里寫(xiě)的co函數(shù)只適用于yield后跟promise。

co庫(kù)的簡(jiǎn)單實(shí)現(xiàn)

await/async

ES7中提供async函數(shù)垒拢,利用它旬迹,不需要依賴(lài)co庫(kù),也一樣可以解決這個(gè)問(wèn)題求类。
使用方法和 co 非常類(lèi)似奔垦,同時(shí)也支持同步寫(xiě)法的異常捕獲。async的返回值為Promise 對(duì)象尸疆。

function a() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(1)
    }, 2000)
  })
}

var b = async function() {
  var val = await a()
  console.log(val)
}

b()

使用await/async或者Generator進(jìn)行http請(qǐng)求的時(shí)候經(jīng)常是配合這Promise的椿猎。

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

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value)
}

asyncPrint('hello world', 50);

async既可以處理promise又可以處理普通的惶岭,不過(guò)此時(shí)相當(dāng)于同步,而且更有語(yǔ)義化犯眠。

async function asyncPrint() {
  await yibu1();
  await yibu2();
}

使用async/await yibu1請(qǐng)求完了再去請(qǐng)求yibu2按灶。如果使用Promise。是不等yibu1的結(jié)果就去請(qǐng)求yibu2筐咧。就等于async可以把本身的回調(diào)或者then里的內(nèi)容拿出來(lái)當(dāng)做同步代碼寫(xiě)鸯旁。但是一般http請(qǐng)求函數(shù)都沒(méi)有返回值,只有請(qǐng)求有了結(jié)果才會(huì)有結(jié)果量蕊,所以await后一般跟著promise铺罢。不然await也不知道異步函數(shù)請(qǐng)求結(jié)束了呀。

yield 命令后面只能是 Thunk 函數(shù)或 Promise 對(duì)象残炮,而 async 函數(shù)的 await 命令后面畏铆,可以跟 Promise 對(duì)象和原始類(lèi)型的值(數(shù)值、字符串和布爾值吉殃,但這時(shí)等同于同步操作)。

所以

async function hhh () {
    var ll = await setTimeout(function() {
        console.log('ss')
    }, 3000)
    console.log('ll')
}
hhh()

回調(diào)主要有兩個(gè)問(wèn)題楷怒,信任問(wèn)題和順序問(wèn)題蛋勺,Promise解決信任問(wèn)題,Generator解決順序問(wèn)題鸠删。但我認(rèn)為Generator也不是把順序問(wèn)題完完全全的解決了抱完。因?yàn)槿绻?/p>

async function async1(value, ms) {
  console.log(value)
  await new Promise()
  async function async2(value, ms) {
      // 
  }
  console.log(value1)
} 

假設(shè) console.log(value1)本身是一個(gè)很費(fèi)時(shí)的同步任務(wù),并且不需要等待async2執(zhí)行完成刃泡。這時(shí)候巧娱,我們還是會(huì)先執(zhí)行console.log(value1) ,后執(zhí)行async2的回調(diào)烘贴。await會(huì)使得async函數(shù)卡住不動(dòng)禁添,所以只能解決一層一層的嵌套的順序問(wèn)題,不能解決這種同一級(jí)別的順序問(wèn)題桨踪。
async函數(shù)的返回值是一個(gè)Promise老翘,上面這個(gè)問(wèn)題可以改為

async function async1(value, ms) {
  console.log(value)
  await new Promise()
  console.log(value1)
} 
async1().then(function(){
  async2(value, ms) 
})

四種異步解決方案
回調(diào)與generate

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市锻离,隨后出現(xiàn)的幾起案子铺峭,更是在濱河造成了極大的恐慌,老刑警劉巖汽纠,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卫键,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡虱朵,警方通過(guò)查閱死者的電腦和手機(jī)莉炉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)钓账,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人呢袱,你說(shuō)我怎么就攤上這事官扣。” “怎么了羞福?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵惕蹄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我治专,道長(zhǎng)卖陵,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任张峰,我火速辦了婚禮泪蔫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘喘批。我一直安慰自己撩荣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布饶深。 她就那樣靜靜地躺著餐曹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪敌厘。 梳的紋絲不亂的頭發(fā)上台猴,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音俱两,去河邊找鬼饱狂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛宪彩,可吹牛的內(nèi)容都是我干的休讳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼毯焕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼衍腥!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起纳猫,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤婆咸,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后芜辕,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體尚骄,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年侵续,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了倔丈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片憨闰。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖需五,靈堂內(nèi)的尸體忽然破棺而出鹉动,到底是詐尸還是另有隱情,我是刑警寧澤宏邮,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布泽示,位于F島的核電站,受9級(jí)特大地震影響蜜氨,放射性物質(zhì)發(fā)生泄漏械筛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一飒炎、第九天 我趴在偏房一處隱蔽的房頂上張望埋哟。 院中可真熱鬧,春花似錦郎汪、人聲如沸赤赊。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)砍鸠。三九已至,卻和暖如春耕驰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背录豺。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工朦肘, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人双饥。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓媒抠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親咏花。 傳聞我的和親對(duì)象是個(gè)殘疾皇子趴生,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

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

  • 弄懂js異步 講異步之前,我們必須掌握一個(gè)基礎(chǔ)知識(shí)-event-loop昏翰。 我們知道JavaScript的一大特點(diǎn)...
    DCbryant閱讀 2,711評(píng)論 0 5
  • 異步編程對(duì)JavaScript語(yǔ)言太重要苍匆。Javascript語(yǔ)言的執(zhí)行環(huán)境是“單線程”的,如果沒(méi)有異步編程棚菊,根本...
    呼呼哥閱讀 7,311評(píng)論 5 22
  • 歡迎閱讀專(zhuān)門(mén)探索 JavaScript 及其構(gòu)建組件的系列文章的第四章浸踩。 在識(shí)別和描述核心元素的過(guò)程中,我們還分享...
    OSC開(kāi)源社區(qū)閱讀 1,154評(píng)論 1 10
  • 你不知道JS:異步 第三章:Promises 接上篇3-1 錯(cuò)誤處理(Error Handling) 在異步編程中...
    purple_force閱讀 1,398評(píng)論 0 2
  • 前言 上一章講解了node如何通過(guò)事件循環(huán)實(shí)現(xiàn)異步统求,包括與各種IO多路復(fù)用搭配實(shí)現(xiàn)的異步IO已經(jīng)與IO無(wú)關(guān)的異步A...
    白昔月閱讀 9,782評(píng)論 0 6