js異步處理(一)——理解異步

我對(duì)異步的好奇心起于學(xué)習(xí)Promise時(shí)老是一知半解岸浑,最近在看《你所不知道的js(中)》泼返,書(shū)中對(duì)異步這部分的講解還是很到位的们镜,所以結(jié)合自己的理解整理一下相關(guān)知識(shí)點(diǎn)币叹。
本文將從是什么、為什么模狭、怎么樣這三步式來(lái)講這個(gè)問(wèn)題颈抚。

一、什么是異步嚼鹉?

我們一般喜歡把異步和同步贩汉、并行拿出來(lái)比較,我以前的理解總是很模糊锚赤,總是生硬地記著“同步就是排隊(duì)執(zhí)行匹舞,異步就是一起執(zhí)行”,現(xiàn)在一看线脚,當(dāng)初簡(jiǎn)直就是傻赐稽,所以我們第一步先把這三個(gè)概念搞清楚,我不太喜歡看網(wǎng)上有些博客里很含糊地說(shuō)“xxxx是同步酒贬,xxxx是異步”,還有舉什么通俗的例子翠霍,其實(shí)對(duì)不懂的人來(lái)說(shuō)還是懵逼锭吨。

首先我們要知道這一切的根源都是“Javascript是單線程”,也就是一次只能做一件事寒匙,那么為什么是單線程呢零如?因?yàn)閖s渲染在瀏覽器上,包含了許多與用戶的交互锄弱,如果是多線程考蕾,那么試想一個(gè)場(chǎng)景:一個(gè)線程在某個(gè)DOM上添加內(nèi)容,而另一個(gè)線程刪除這個(gè)DOM会宪,那么瀏覽器要如何反應(yīng)呢肖卧?這就亂套了。

單線程下所有的任務(wù)都是需要排隊(duì)的掸鹅,而這些任務(wù)分為兩種:同步任務(wù)和異步任務(wù)塞帐,同步任務(wù)就是在主線程上排隊(duì)執(zhí)行的任務(wù)拦赠,只有前一個(gè)任務(wù)執(zhí)行完畢,才能執(zhí)行后一個(gè)任務(wù)葵姥;異步任務(wù)指的是荷鼠,不進(jìn)入主線程、而進(jìn)入任務(wù)隊(duì)列(task queue)的任務(wù)榔幸,只有任務(wù)隊(duì)列通知主線程允乐,某個(gè)異步任務(wù)可以執(zhí)行了,該任務(wù)才會(huì)進(jìn)入主線程執(zhí)行削咆。所以說(shuō)同步執(zhí)行其實(shí)也是一種只有主線程的異步執(zhí)行牍疏。這里有一個(gè)視頻關(guān)于異步操作是如何被執(zhí)行的,講得非常好《what the hack is event loop》态辛,我給大家畫(huà)個(gè)圖再來(lái)理解一下麸澜。

event-loop.png

這里補(bǔ)充說(shuō)明下不同的異步操作添加到任務(wù)隊(duì)列的時(shí)機(jī)不同,如 onclick, setTimeout, ajax 處理的方式都不同奏黑,這些異步操作是由瀏覽器內(nèi)核的 webcore 來(lái)執(zhí)行的炊邦,webcore 包含上面提到的3種 webAPI,分別是 DOM Binding熟史、timer馁害、network模塊。
onclick 由瀏覽器內(nèi)核的 DOM Binding 模塊來(lái)處理蹂匹,當(dāng)事件觸發(fā)的時(shí)候碘菜,回調(diào)函數(shù)會(huì)立即添加到任務(wù)隊(duì)列中。
setTimeout 會(huì)由瀏覽器內(nèi)核的 timer 模塊來(lái)進(jìn)行延時(shí)處理限寞,當(dāng)時(shí)間到達(dá)的時(shí)候忍啸,才會(huì)將回調(diào)函數(shù)添加到任務(wù)隊(duì)列中。
ajax 則會(huì)由瀏覽器內(nèi)核的 network 模塊來(lái)處理履植,在網(wǎng)絡(luò)請(qǐng)求完成返回之后计雌,才將回調(diào)添加到任務(wù)隊(duì)列中。
最后再來(lái)說(shuō)下并行玫霎,并行是關(guān)于能夠同時(shí)發(fā)生的事情凿滤,是一種多線程的運(yùn)行機(jī)制,而不管同步異步都是單線程的庶近。

二翁脆、為什么要用異步操作

這個(gè)很好理解,同步下前一個(gè)事件執(zhí)行完了才能執(zhí)行后一個(gè)事件鼻种,那么要是遇到Ajax請(qǐng)求這種耗時(shí)很長(zhǎng)的反番,那頁(yè)面在這段時(shí)間就沒(méi)法操作了,卡在那兒,更有甚者恬口,萬(wàn)一這個(gè)請(qǐng)求由于某種原因一直沒(méi)有完成校读,那頁(yè)面就block了,很不友好祖能。

三歉秫、如何實(shí)現(xiàn)異步

我們可以通過(guò)回調(diào)函數(shù)Promise养铸、生成器雁芙、Async/Await等來(lái)實(shí)現(xiàn)異步。
今天我們先說(shuō)最基礎(chǔ)的回調(diào)函數(shù)處理方法來(lái)實(shí)現(xiàn)钞螟,列舉幾個(gè)大家熟悉的使用場(chǎng)景兔甘,比如:ajax請(qǐng)求、IO操作鳞滨、定時(shí)器洞焙。

ajax(url, function(){
   //這就是回調(diào)函數(shù)
});
setTimeOut(function(){
   //回調(diào)函數(shù)
}, 1000)

回調(diào)本身是比較好用的,但是隨著Javascript越來(lái)越成熟拯啦,對(duì)于異步編程領(lǐng)域的發(fā)展澡匪,回調(diào)已經(jīng)不夠用了,體現(xiàn)在以下幾點(diǎn):

1褒链、大腦處理程序是順序的唁情,對(duì)于復(fù)雜的回調(diào)函數(shù)會(huì)不易理解,我們需要一種更同步甫匹、更順序的方式來(lái)表達(dá)異步甸鸟。
舉例說(shuō)明:

//回調(diào)函數(shù)實(shí)現(xiàn)兩數(shù)相加
function add(getX,  getY, cb){
  var x, y;
  getX(function(xVal){
    x=xVal;
    if(y!=undefined){
      cb(x+y);
    }
  });
  getY(function(){
    y=yVal;
    if(x!=undefined){
      cb(x+y);
    }
  });
}
add(fetchX, fetchY, function(sum){
  console.log(sum);
})

//Promise實(shí)現(xiàn)兩數(shù)相加
function add(xPromise, yPromise){
  return Promise.all([xPromise, yPromise])
  .then(function(values){
    return value[0] + value[1];
  });
}
//fetchX()、fetchY()返回相應(yīng)值的Promise
add(fetchX(), fetchY())
  .then(function(sum){
    console.log(sum);
  })

只看結(jié)構(gòu)是不是Promise的寫(xiě)法更順序話一些兵迅。
2抢韭、回調(diào)一般會(huì)把控制權(quán)交給第三方,從而帶來(lái)信任問(wèn)題恍箭,比如:

  • 調(diào)用回調(diào)過(guò)早
  • 調(diào)用回調(diào)過(guò)晚(或未調(diào)用)
  • 調(diào)用回調(diào)次數(shù)過(guò)多或過(guò)少
  • 未能傳遞所需的環(huán)境和參數(shù)
  • 吞掉可能出現(xiàn)的錯(cuò)誤和異常

而Promise的特性就有效地解決了這些問(wèn)題刻恭,它是如何解決的呢?

調(diào)用回調(diào)過(guò)早

這種顧慮主要是代碼是否會(huì)引入類(lèi)Zalgo效應(yīng)季惯,也就是一個(gè)任務(wù)有時(shí)會(huì)同步完地成吠各,而有時(shí)會(huì)異步地完成臀突,這將導(dǎo)致竟合狀態(tài)勉抓。
Promise被定義為不能受這種顧慮的影響,因?yàn)榧幢闶橇⒓赐瓿傻腜romise(比如 new Promise(function(resolve){ resolve(42); }))也不可能被同步地 監(jiān)聽(tīng)候学。也就是說(shuō)藕筋,但你在Promise上調(diào)用then(..)的時(shí)候,即便這個(gè)Promise已經(jīng)被解析了梳码,你給then(..)提供的回調(diào)也將總是被異步地調(diào)用隐圾。

調(diào)用回調(diào)過(guò)晚

當(dāng)一個(gè)Promise被調(diào)用時(shí)伍掀,這個(gè)Promise 上的then注冊(cè)的回調(diào)函數(shù)都會(huì)在下一個(gè)異步時(shí)機(jī)點(diǎn)上,按順序地暇藏,被立即調(diào)用蜜笤。這些回調(diào)中的任意一個(gè)都無(wú)法影響或延誤對(duì)其它回調(diào)的調(diào)用。
舉例說(shuō)明:

p.then( function(){
    p.then( function(){
        console.log( "C" );
    } );
    console.log( "A" );
} );
p.then( function(){
    console.log( "B" );
} );
// A B C

為什么“C”沒(méi)有排到“B”的前面盐碱?因?yàn)橐驗(yàn)椤癈”所處的.then回調(diào)函數(shù)是在下一個(gè)事件循環(huán)tick把兔。

回調(diào)未調(diào)用

這是一個(gè)很常見(jiàn)的顧慮。Promise用幾種方式解決它瓮顽。
首先县好,當(dāng)Promise被解析后,在代碼不出錯(cuò)的情況下它一定會(huì)告知你解析結(jié)果暖混。如果代碼有錯(cuò)誤缕贡,歸類(lèi)于后面的“吞掉錯(cuò)誤或異常”中拣播。
那如果Promise本身不管怎樣永遠(yuǎn)沒(méi)有被解析呢晾咪?那么Promise會(huì)用Promise.race來(lái)解決。
看代碼示例:

// 一個(gè)使Promise超時(shí)的工具
function timeoutPromise(delay) {
    return new Promise( function(resolve,reject){
        setTimeout( function(){
            reject( "Timeout!" );
        }, delay );
    } );
}

// 為`foo()`設(shè)置一個(gè)超時(shí)
Promise.race( [
    foo(),                    // 嘗試調(diào)用`foo()`
    timeoutPromise( 3000 )    // 給它3秒鐘
] )
.then(
    function(){
        // `foo(..)`及時(shí)地完成了诫尽!
    },
    function(err){
        // `foo()`不是被拒絕了禀酱,就是它沒(méi)有及時(shí)完成
        // 那么可以考察`err`來(lái)知道是哪種情況
    }
);
調(diào)用次數(shù)過(guò)少或過(guò)多

正常是調(diào)用一次,“過(guò)少”就是未被調(diào)用牧嫉,參考上文剂跟;“過(guò)多”的情況也很容易理解。Promise的定義方式使得它只能被決議一次酣藻,如果出于某種情況決議了多次曹洽,Promise也只會(huì)接受第一次決議,并忽略后續(xù)調(diào)用辽剧。

未能傳遞所需的參數(shù)/環(huán)境值

Promise只會(huì)有一個(gè)解析結(jié)果(完成或拒絕)送淆。如果沒(méi)有用一個(gè)值明確地解析它,它的值就是undefined怕轿,就像JS中常見(jiàn)的那樣偷崩。

吞掉錯(cuò)誤或異常

Promise中異常會(huì)被捕獲,并且使這個(gè)Promise被拒絕撞羽。
舉個(gè)例子:

var p = new Promise( function(resolve,reject){
    foo.bar();    // `foo`沒(méi)有定義阐斜,所以這是一個(gè)錯(cuò)誤!
    resolve( 42 );    // 永遠(yuǎn)不會(huì)跑到這里 :(
} );

p.then(
    function fulfilled(){
        // 永遠(yuǎn)不會(huì)跑到這里 :(
    },
    function rejected(err){
        // `err`將是一個(gè)來(lái)自`foo.bar()`那一行的`TypeError`異常對(duì)象
    }
);

Promise就先說(shuō)到這里诀紊,關(guān)于PromiseAPI及其源碼還有生成器谒出、Async/Await 在后續(xù)文章中整理報(bào)道。
【寫(xiě)得不好的地方請(qǐng)大膽吐槽,非常感謝大家?guī)疫M(jìn)步笤喳∥樱】

參考資料:
阮一峰e(cuò)vent-loop
王福朋深入理解javascript異步系列一
你不知道的javascript
你不懂JS: 異步與性能 第三章: Promise(上)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酪耕,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忍抽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡董朝,警方通過(guò)查閱死者的電腦和手機(jī)鸠项,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)子姜,“玉大人祟绊,你說(shuō)我怎么就攤上這事「绮叮” “怎么了牧抽?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)遥赚。 經(jīng)常有香客問(wèn)我扬舒,道長(zhǎng),這世上最難降的妖魔是什么凫佛? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任讲坎,我火速辦了婚禮,結(jié)果婚禮上愧薛,老公的妹妹穿的比我還像新娘晨炕。我一直安慰自己,他們只是感情好毫炉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布瓮栗。 她就那樣靜靜地躺著,像睡著了一般瞄勾。 火紅的嫁衣襯著肌膚如雪费奸。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,737評(píng)論 1 305
  • 那天进陡,我揣著相機(jī)與錄音愿阐,去河邊找鬼。 笑死四濒,一個(gè)胖子當(dāng)著我的面吹牛换况,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播盗蟆,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼戈二,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了喳资?” 一聲冷哼從身側(cè)響起觉吭,我...
    開(kāi)封第一講書(shū)人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仆邓,沒(méi)想到半個(gè)月后鲜滩,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡节值,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年徙硅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搞疗。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嗓蘑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出匿乃,到底是詐尸還是另有隱情桩皿,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布幢炸,位于F島的核電站泄隔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏宛徊。R本人自食惡果不足惜佛嬉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望闸天。 院中可真熱鬧巷燥,春花似錦、人聲如沸号枕。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)葱淳。三九已至钝腺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赞厕,已是汗流浹背艳狐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留皿桑,地道東北人毫目。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓蔬啡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親镀虐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子箱蟆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

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

  • 弄懂js異步 講異步之前,我們必須掌握一個(gè)基礎(chǔ)知識(shí)-event-loop刮便。 我們知道JavaScript的一大特點(diǎn)...
    DCbryant閱讀 2,712評(píng)論 0 5
  • 你不知道JS:異步 第三章:Promises 在第二章空猜,我們指出了采用回調(diào)來(lái)表達(dá)異步和管理并發(fā)時(shí)的兩種主要不足:缺...
    purple_force閱讀 2,068評(píng)論 0 4
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券恨旱,享受所有官網(wǎng)優(yōu)惠辈毯,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 11,026評(píng)論 26 95
  • 你不知道JS:異步 第三章:Promises 接上篇3-1 錯(cuò)誤處理(Error Handling) 在異步編程中...
    purple_force閱讀 1,398評(píng)論 0 2
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券搜贤,享受所有官網(wǎng)優(yōu)惠谆沃,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 8,681評(píng)論 0 29