前言
????在Javascript這樣類(lèi)型的語(yǔ)言中編程最重要但最常被人誤解的部分之一,就是如何控制在一段時(shí)間內(nèi)程序的行為次序.同時(shí),JavaScript中的異步,也經(jīng)常被人和并行搞混.今天,我們來(lái)談一下JavaScript中的異步.
自
JS開(kāi)始以來(lái),異步編程一直存在.然而,但是大多數(shù)JS開(kāi)發(fā)人員從未真正仔細(xì)考慮過(guò)在程序中如何以及為何出現(xiàn)問(wèn)題,也沒(méi)有去探索各種其他處理方法。 比較好的方法一直是稀里糊涂的使用回調(diào)函數(shù).到今天為止,許多人會(huì)堅(jiān)持認(rèn)為回調(diào)使用起來(lái)就已經(jīng)綽綽有余了.
什么是異步?
????首先,一段JavaScript程序是由多個(gè)塊(chunk)組成的,最常見(jiàn)的塊就是function–函數(shù).
我們把一段時(shí)間內(nèi),程序要執(zhí)行的任務(wù)分為兩部分:?
1.執(zhí)行部分(現(xiàn)在執(zhí)行的),2.等待部分(剩下的將來(lái)要執(zhí)行的).而我們面臨的問(wèn)題是,當(dāng)現(xiàn)在執(zhí)行部分執(zhí)行完后,程序并不是嚴(yán)格地立馬去完成等待部分.換句話(huà)就是,這些塊是異步執(zhí)行的.我們不會(huì)像預(yù)期的那樣阻塞地完成一個(gè)接一個(gè)的任務(wù).
例如:
//ajax是某些JavaScript框架(如:jQurey)中實(shí)現(xiàn)Ajax的函數(shù)
let data =ajax( "http://some.url.1" );
//控制臺(tái)輸出data內(nèi)容
console.log(data)
如果運(yùn)行這段JavaScript代碼會(huì)發(fā)現(xiàn),打印出來(lái)的data通常沒(méi)有我們想要的ajax請(qǐng)求結(jié)果.
這是因?yàn)?/p>
,Ajax請(qǐng)求并不是同步(synchronously,相對(duì)于異步asynchronously)完成的,當(dāng)執(zhí)行console.log()的時(shí)候,我們想要的data還沒(méi)有返回.我們想要的其實(shí)是ajax(...)函數(shù)能夠阻塞,一直到請(qǐng)求結(jié)果返回,最簡(jiǎn)單的解決方法就是回調(diào)(callback).
//回調(diào)方式的一個(gè)示例,具體回調(diào)方式根據(jù)具體來(lái)定.
ajax( "http://some.url.1",functionmyCallbackFunction(data){
console.log( data );
} );
這時(shí)我們會(huì)發(fā)現(xiàn),data就是我們想要的了.
注意:我們是可以同步地請(qǐng)求
Ajax的,比如:jQurey中的ajax()將async: false加入設(shè)置.但是這樣做的后果就是瀏覽器的UI操作(按鈕,滾動(dòng)等)以及用戶(hù)交互等都會(huì)被阻塞等待鎖死.我們應(yīng)該避免這種情況,一團(tuán)亂麻的回調(diào)函數(shù)也不應(yīng)成為使用同步Ajax的理由.
記下來(lái)我們?cè)倏紤]另一個(gè)例子幫助理解:
functionnow() {
return21;
}
function later() {
answer=answer* 2;
console.log("answer:",answer);
}
varanswer= now();
setTimeout(later, 1000 ); // answer: 42
我們?cè)儆脛偛诺乃悸啡ダ斫膺@個(gè)程序:分為兩個(gè)部分:執(zhí)行部分,等待部分.
執(zhí)行部分是:
//回調(diào)方式的一個(gè)示例,具體回調(diào)方式根據(jù)具體來(lái)定.
ajax( "http://some.url.1",functionmyCallbackFunction(data){
console.log( data );
} );
等待部分就是later()中的內(nèi)容:
answer=answer* 2;
console.log( "answer:",answer);
執(zhí)行部分會(huì)立刻執(zhí)行,而setTimeout(...)會(huì)設(shè)定一個(gè)事件(timeout事件),在1000ms后執(zhí)行l(wèi)ater().就像這樣,每當(dāng)我們?cè)趂unction中寫(xiě)一段代碼,并讓它在事件(timer,鼠標(biāo)事件,Ajax響應(yīng)等)響應(yīng)后執(zhí)行,我們就創(chuàng)造了一個(gè)等待部分,也就是在程序中使用了異步.
Event Loop
????雖然我們?cè)谶@里談異步,但是,直到ES6*,JavaScript本身并沒(méi)有內(nèi)置異步的概念.聽(tīng)起來(lái)很震驚,但事實(shí)確實(shí)是這樣的.我們會(huì)問(wèn):那我們討論的異步是怎么實(shí)現(xiàn)的呢??
我們都知道的是
JavaScript引擎從來(lái)不是獨(dú)立執(zhí)行,總要依賴(lài)于一個(gè)環(huán)境,比如,我們最熟悉的web瀏覽器.以及服務(wù)器上的Node.js.這些環(huán)境會(huì)用一個(gè)機(jī)制來(lái)隨時(shí)間使用JavaScript引擎處理我們的多個(gè)程序塊,這個(gè)機(jī)制我們管它叫Event Loop.
換句話(huà)說(shuō),
JavaScript引擎并不知道什么時(shí)候執(zhí)行,而是被執(zhí)行環(huán)境的線(xiàn)程來(lái)安排處理哪些程序塊,執(zhí)行環(huán)境根據(jù)事件來(lái)調(diào)度JavaScript引擎處理.
那么什么是
Event Loop呢?
我們通過(guò)一段偽代碼來(lái)了解它的概念:
//eventLoop是事件排成的先進(jìn)先出的隊(duì)列(queue)
vareventLoop= [ ];
var event;
while(true) {
// 處理完一個(gè)事件
if (eventLoop.length > 0) {
// 獲取隊(duì)列中的下個(gè)動(dòng)作
event=eventLoop.shift();
// 處理剛才取出的動(dòng)作
try {
event();
}
catch (err) {
reportError(err);
}
}
}
我們通過(guò)這段偽代碼大體了解它的機(jī)制.我們有一個(gè)循環(huán),循環(huán)的每一個(gè)迭代中,如果在等待隊(duì)列中存在事件,就會(huì)被取出并處理,event()就是各種回調(diào)函數(shù).
因此,到這兒我們就可以明白了,
setTimeout(..)不是把設(shè)定好的回調(diào)函數(shù)安排到event loop中,而是將一個(gè)計(jì)時(shí)器(timer)安排在event loop中,當(dāng)計(jì)時(shí)器到期,執(zhí)行環(huán)境將回調(diào)推入event loop,這樣,在將來(lái)某個(gè)時(shí)間會(huì)被取出并執(zhí)行.
假如,現(xiàn)在
event loop中已經(jīng)存在20個(gè)等待的成員,那么這個(gè)回調(diào)就應(yīng)該等待,通常沒(méi)有方法能將他移動(dòng)到隊(duì)列頭部,讓他立馬執(zhí)行.這樣就產(chǎn)生了,哪怕用了setTimeout(..),指定的回調(diào)并不會(huì)在指定時(shí)間后立即執(zhí)行的現(xiàn)象,當(dāng)然也不會(huì)提前,至于是否要等待,等待多久,要根據(jù)具體情況來(lái)說(shuō).
注意:之所以說(shuō)是”直到ES6”,是因?yàn)镋S6引入了Promise機(jī)制,ES6通過(guò)Promise將event loop的工作機(jī)制納入到了JavaScript引擎的工作范圍,而不只是執(zhí)行環(huán)境的工作.關(guān)于Promise以后有機(jī)會(huì)再談.
并行
有一個(gè)常見(jiàn)的現(xiàn)象就是,人們經(jīng)常把”異步”和”并行”混為一談,其實(shí)他們大不相同.”異步”,指的是執(zhí)行部分和等待部分中間有時(shí)間差,并不是立即執(zhí)行.而并行則是指一起執(zhí)行.
并行計(jì)算中最常見(jiàn)的單位是
進(jìn)程(process)和線(xiàn)程(thread),進(jìn)程和線(xiàn)程之間可以是獨(dú)立執(zhí)行,也可以在一個(gè)處理器中,或者一臺(tái)電腦中同時(shí)執(zhí)行.通常,多個(gè)線(xiàn)程可以共享單個(gè)進(jìn)程的內(nèi)存.
相比之下
,event loop是將一個(gè)工作分解成多個(gè)任務(wù),并組成隊(duì)列串行執(zhí)行,不能并行訪(fǎng)問(wèn)和更改共享的內(nèi)存.它的并行性和”串行性”可以在不同線(xiàn)程下的event loop上體現(xiàn)(一個(gè)線(xiàn)程可以創(chuàng)立一個(gè)event loop,不同線(xiàn)程下的event loop具有并行性,單個(gè)event loop具有串行性).
并行地
執(zhí)行線(xiàn)程和異步地交錯(cuò)處理事件在粒度級(jí)別上有著很大的不同.線(xiàn)程是表達(dá)式操作級(jí)別,而異步是函數(shù)級(jí)別。