JS異步那些事 一 (基礎(chǔ)知識(shí))

JS異步那些事 一 (基礎(chǔ)知識(shí))
JS異步那些事 二 (分布式事件)
JS異步那些事 三 (Promise)
JS異步那些事 四(HTML 5 Web Workers)
JS異步那些事 五 (異步腳本加載)

js事件概念

異步回調(diào):

首先了講講js中 兩個(gè)方法 setTimeout()和 setInterval()

定義和用法:

setTimeout() 方法用于在指定的毫秒數(shù)后調(diào)用函數(shù)或計(jì)算表達(dá)式锌唾。

語(yǔ)法:

setTimeout(callback,time)

callback 必需码秉。要調(diào)用的函數(shù)后要執(zhí)行的 JavaScript 代碼串啦吧。
time 必需循衰。在執(zhí)行代碼前需等待的毫秒數(shù)悲酷。

setInterval() 方法和setTimeout很相似嘶卧,可按照指定的周期(以毫秒計(jì))來(lái)調(diào)用函數(shù)或計(jì)算表達(dá)式言询。

<script type="text/javascript">
function timeCount()
{console.log("this is setTimeout");
setTimeout("timeCount()",1000);

}

function timeCount2(){
console.log("this is setInterval");
}
setInterval("timeCount2()",1000);

timeCount();
timeCount2();
</script>

比如上述代碼就是可以每隔1000毫秒延遲執(zhí)行timecount函數(shù)俯萎,不同的是后者是周期的執(zhí)行timecount函數(shù),
SetInterval為自動(dòng)重復(fù)运杭,setTimeout不會(huì)重復(fù)夫啊。

線程阻塞

JavaScript引擎是單線程運(yùn)行的,瀏覽器無(wú)論在什么時(shí)候都只且只有一個(gè)線程在運(yùn)行JavaScript程序.

<script type="text/javascript"> 
function f() { console.log("hello world");} 
var t = new Date();  //運(yùn)行5秒 
while(true) { 
if(new Date() - t > 5000) { 
break; } 
} 
setTimeout(f, 1000); 
</script>

執(zhí)行上述代碼,可以發(fā)現(xiàn)辆憔,總的運(yùn)行時(shí)間幾乎要6秒多撇眯,因?yàn)槭菃尉€程,會(huì)在while循環(huán)里面消耗5秒的時(shí)間虱咧,然后才去執(zhí)行settimeout函數(shù)熊榛。

隊(duì)列

瀏覽器是基于一個(gè)事件循環(huán)的模型,在這里面彤钟,可以有多個(gè)任務(wù)隊(duì)列来候,比如render是一個(gè)隊(duì)列,響應(yīng)用戶輸入是一個(gè)逸雹,script執(zhí)行是一個(gè)营搅。任務(wù)隊(duì)列里放的是任務(wù),同一個(gè)任務(wù)來(lái)源的任務(wù)肯定在同一個(gè)任務(wù)隊(duì)列里梆砸。任務(wù)有優(yōu)先級(jí)转质,鼠標(biāo)或鍵盤響應(yīng)事件優(yōu)先級(jí)高,大概是其他任務(wù)的3倍帖世。

而我們常用的setTimeout函數(shù)休蟹,其本質(zhì)上也就是向這個(gè)任務(wù)隊(duì)列添加回調(diào)函數(shù),JavaScript引擎一直等待著任務(wù)隊(duì)列中任務(wù)的到來(lái).由于單線程關(guān)系,這些任務(wù)得進(jìn)行排隊(duì),一個(gè)接著一個(gè)被引擎處理.

如果隊(duì)列非空,引擎就從隊(duì)列頭取出一個(gè)任務(wù),直到該任務(wù)處理完,即返回后引擎接著運(yùn)行下一個(gè)任務(wù),在任務(wù)沒(méi)返回前隊(duì)列中的其它任務(wù)是沒(méi)法被執(zhí)行的.

異步函數(shù)類型

異步IO:

首先來(lái)看看很典型的一個(gè)例子 ajax

<script type="text/javascript"> 
var  ajax = new XMLHttpRequest;
ajax.open("GET",url); 
ajax.send(null);
ajax.onreadystatechange = function () {
    if (request.readyState === 4) {
        if (request.status === 200) {
            return success(request.responseText);
        } else {
            return fail(request.status);
        }
    }
}
</script>

異步計(jì)時(shí):

setTimeout,setInterval都是基于事件驅(qū)動(dòng)型的赂弓,通常瀏覽器不會(huì)給這個(gè)太快的速度绑榴,一般是200次/秒,效率太低了是吧如果遇到有密集型的運(yùn)算的話盈魁,那就呵呵了翔怎。但是在node.js中還有process.nextTick()這個(gè)強(qiáng)大的東西,運(yùn)行的速度將近10萬(wàn)次/秒杨耙,很可觀赤套。

process.nextTick(callback)

功能:在事件循環(huán)的下一次循環(huán)中調(diào)用 callback 回調(diào)函數(shù)。效果是將一個(gè)函數(shù)推遲到代碼書寫的下一個(gè)同步方法執(zhí)行完畢時(shí)或異步方法的事件回調(diào)函數(shù)開始執(zhí)行時(shí)珊膜;與setTimeout(fn, 0) 函數(shù)的功能類似容握,但它的效率高多了。

基于node.js的事件循環(huán)分析车柠,每一次循環(huán)就是一次tick剔氏,每一次tick時(shí),v8引擎從事件隊(duì)列中取出所有事件依次進(jìn)行處理堪遂,如果遇到nextTick事件介蛉,則將其加入到事件隊(duì)尾,等待下一次tick到來(lái)時(shí)執(zhí)行溶褪;造成的結(jié)果是币旧,nextTick事件被延遲執(zhí)行;

nextTick的確是把某任務(wù)放在隊(duì)列的最后(array.push)
nodejs在執(zhí)行任務(wù)時(shí)猿妈,會(huì)一次性把隊(duì)列中所有任務(wù)都拿出來(lái)吹菱,依次執(zhí)行
如果全部順利完成,則刪除剛才取出的所有任務(wù)彭则,等待下一次執(zhí)行
如果中途出錯(cuò)鳍刷,則刪除已經(jīng)完成的任務(wù)和出錯(cuò)的任務(wù),等待下次執(zhí)行
如果第一個(gè)就出錯(cuò)俯抖,則throw error

下面看一下應(yīng)用場(chǎng)景(包含計(jì)算密集型操作输瓜,將其進(jìn)行遞歸處理,而不阻塞進(jìn)程):

var http = require('http');
var wait = function (mils) {
    var now = new Date;
    while (new Date - now <= mils);
};
function compute() {
    // performs complicated calculations continuously
    console.log('start computing');
    wait(1000);
    console.log('working for 1s, nexttick');
    process.nextTick(compute);
}
http.createServer(function (req, res) {
    console.log('new request');
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World');
}).listen(5000, '127.0.0.1');
compute();

異步錯(cuò)誤處理

異步異常的特點(diǎn)

由于js的回調(diào)異步特性芬萍,無(wú)法通過(guò)try catch來(lái)捕捉所有的異常:

try {
  process.nextTick(function () {
    foo.bar();
  });
} catch (err) {
  //can not catch it
}

而對(duì)于web服務(wù)而言尤揣,其實(shí)是非常希望這樣的:

//express風(fēng)格的路由
app.get('/index', function (req, res) {
  try {
    //業(yè)務(wù)邏輯
  } catch (err) {
    logger.error(err);
    res.statusCode = 500;
    return res.json({success: false, message: '服務(wù)器異常'});
  }
});

如果try catch能夠捕獲所有的異常,這樣我們可以在代碼出現(xiàn)一些非預(yù)期的錯(cuò)誤時(shí)柬祠,能夠記錄下錯(cuò)誤的同時(shí)北戏,友好的給調(diào)用者返回一個(gè)500錯(cuò)誤÷祝可惜嗜愈,try catch無(wú)法捕獲異步中的異常旧蛾。
難道我們就這樣放棄了么? 其實(shí)還有一個(gè)辦法

onerror事件

我們一般通過(guò)函數(shù)名傳遞的方式(引用的方式)將要執(zhí)行的操作函數(shù)傳遞給onerror事件蠕嫁,如
window.onerror=reportError;
window.onerror=function(){alert('error')}

但我們可能不知道該事件觸發(fā)時(shí)還帶有三個(gè)默認(rèn)的參數(shù)锨天,他們分別是錯(cuò)誤信息,錯(cuò)誤頁(yè)面的url和錯(cuò)誤行號(hào)拌阴。

<script type="text/javascript">   
    window.onerror=testError;   
    function testError(){   
    arglen=arguments.length;   
    var errorMsg="參數(shù)個(gè)數(shù):"+arglen+"個(gè)";   
    for(var i=0;i<arglen;i++){   
    errorMsg+="\n參數(shù)"+(i+1)+":"+arguments[i];   
}   
    alert(errorMsg);   
    window.onerror=null;   
    return true;   
}   
function test(){   
    error   
}   
test()   
</script> 

嵌套式回調(diào)的解嵌套

JavaScript中最常見的反模式做法是绍绘,回調(diào)內(nèi)部再嵌套回調(diào)奶镶。

<script type="text/javascript"> 
function checkPassword(username, passwordGuess, callback) {  
  var queryStr = 'SELECT * FROM user WHERE username = ?';  
  db.query(queryStr, username, function (err, result) {  
    if (err) throw err;  
    hash(passwordGuess, function(passwordGuessHash) {  
      callback(passwordGuessHash === result['password_hash']);  
    });  
  });  
} </script> 

這里定義了一個(gè)異步函數(shù)checkPassword迟赃,它觸發(fā)了另一個(gè)異步函數(shù)db.query,而后者又可能觸發(fā)另外一個(gè)異步函數(shù)hash厂镇。它能用纤壁,而且簡(jiǎn)潔明了。但是捺信,如果試圖向其添加新特性酌媒,它就會(huì)變得毛里毛躁、險(xiǎn)象環(huán)生迄靠,比如去處理那個(gè)數(shù)據(jù)庫(kù)錯(cuò)誤秒咨,而不是拋出錯(cuò)誤、記錄嘗試訪問(wèn)數(shù)據(jù)庫(kù)的次數(shù)掌挚、阻塞訪問(wèn)數(shù)據(jù)庫(kù)雨席,等等。
下面我們換一種寫法吠式,雖然這種寫法很啰嗦但是可讀性更高而且更易擴(kuò)展陡厘。

<script type="text/javascript"> 
function checkPassword(username, passwordGuess, callback) {  
  var passwordHash;  
  var queryStr = 'SELECT * FROM user WHERE username = ?';  
  db.query(qyeryStr, username, queryCallback);  
 
  function queryCallback(err, result) {  
    if (err) throw err;  
    passwordHash = result['password_hash'];  
    hash(passwordGuess, hashCallback);  
  }  
 
  function hashCallback(passwordGuessHash) {  
    callback(passwordHash === passwordGuessHash);  
  }  
}  </script> 

在平時(shí)寫嵌套時(shí),我們應(yīng)該盡量避免多層嵌套特占,不然中間某個(gè)地方出錯(cuò)了將會(huì)導(dǎo)致你投入更多的時(shí)間去debug糙置。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市是目,隨后出現(xiàn)的幾起案子谤饭,更是在濱河造成了極大的恐慌,老刑警劉巖懊纳,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件揉抵,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡长踊,警方通過(guò)查閱死者的電腦和手機(jī)功舀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)身弊,“玉大人辟汰,你說(shuō)我怎么就攤上這事列敲。” “怎么了帖汞?”我有些...
    開封第一講書人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵戴而,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我翩蘸,道長(zhǎng)所意,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任催首,我火速辦了婚禮扶踊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘郎任。我一直安慰自己秧耗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開白布舶治。 她就那樣靜靜地躺著分井,像睡著了一般。 火紅的嫁衣襯著肌膚如雪霉猛。 梳的紋絲不亂的頭發(fā)上尺锚,一...
    開封第一講書人閱讀 48,954評(píng)論 1 283
  • 那天,我揣著相機(jī)與錄音惜浅,去河邊找鬼瘫辩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛赡矢,可吹牛的內(nèi)容都是我干的杭朱。 我是一名探鬼主播,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼吹散,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼弧械!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起空民,我...
    開封第一講書人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤刃唐,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后界轩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體画饥,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年浊猾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抖甘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡葫慎,死狀恐怖衔彻,靈堂內(nèi)的尸體忽然破棺而出薇宠,到底是詐尸還是另有隱情,我是刑警寧澤艰额,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布澄港,位于F島的核電站,受9級(jí)特大地震影響柄沮,放射性物質(zhì)發(fā)生泄漏回梧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一祖搓、第九天 我趴在偏房一處隱蔽的房頂上張望狱意。 院中可真熱鬧,春花似錦棕硫、人聲如沸髓涯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蚓再,卻和暖如春滑肉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背摘仅。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工靶庙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人娃属。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓六荒,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親矾端。 傳聞我的和親對(duì)象是個(gè)殘疾皇子掏击,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

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