JS異步那些事 一 (基礎知識)
JS異步那些事 二 (分佈式事件)
JS異步那些事 三 (Promise)
JS異步那些事 四(HTML 5 Web Workers)
JS異步那些事 五 (異步腳本加載)javascript
首先了講講js中 兩個方法 setTimeout()和 setInterval()java
setTimeout() 方法用於在指定的毫秒數後調用函數或計算表達式。
setTimeout(callback,time)
callback 必需。要調用的函數後要執行的 JavaScript 代碼串。
time 必需。在執行代碼前需等待的毫秒數。node
setInterval() 方法和setTimeout很類似,可按照指定的週期(以毫秒計)來調用函數或計算表達式。web
<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毫秒延遲執行timecount函數,不一樣的是後者是週期的執行timecount函數,
SetInterval爲自動重複,setTimeout不會重複。ajax
JavaScript引擎是單線程運行的,瀏覽器不管在何時都只且只有一個線程在運行JavaScript程序.數據庫
<script type="text/javascript"> function f() { console.log("hello world");} var t = new Date(); //運行5秒 while(true) { if(new Date() - t > 5000) { break; } } setTimeout(f, 1000); </script>
執行上述代碼,能夠發現,總的運行時間幾乎要6秒多,由於是單線程,會在while循環裏面消耗5秒的時間,而後纔去執行settimeout函數。express
瀏覽器是基於一個事件循環的模型,在這裏面,能夠有多個任務隊列,好比render是一個隊列,響應用戶輸入是一個,script執行是一個。任務隊列裏放的是任務,同一個任務來源的任務確定在同一個任務隊列裏。任務有優先級,鼠標或鍵盤響應事件優先級高,大概是其餘任務的3倍。json
而咱們經常使用的setTimeout函數,其本質上也就是向這個任務隊列添加回調函數,JavaScript引擎一直等待着任務隊列中任務的到來.因爲單線程關係,這些任務得進行排隊,一個接着一個被引擎處理.segmentfault
若是隊列非空,引擎就從隊列頭取出一個任務,直到該任務處理完,即返回後引擎接着運行下一個任務,在任務沒返回前隊列中的其它任務是無法被執行的.瀏覽器
首先來看看很典型的一個例子 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>
setTimeout,setInterval都是基於事件驅動型的,一般瀏覽器不會給這個太快的速度,通常是200次/秒,效率過低了是吧若是遇到有密集型的運算的話,那就呵呵了。可是在node.js中還有process.nextTick()這個強大的東西,運行的速度將近10萬次/秒,很可觀。
process.nextTick(callback)
功能:在事件循環的下一次循環中調用 callback 回調函數。效果是將一個函數推遲到代碼書寫的下一個同步方法執行完畢時或異步方法的事件回調函數開始執行時;與setTimeout(fn, 0) 函數的功能相似,但它的效率高多了。
基於node.js的事件循環分析,每一次循環就是一次tick,每一次tick時,v8引擎從事件隊列中取出全部事件依次進行處理,若是遇到nextTick事件,則將其加入到事件隊尾,等待下一次tick到來時執行;形成的結果是,nextTick事件被延遲執行;
nextTick的確是把某任務放在隊列的最後(array.push)
nodejs在執行任務時,會一次性把隊列中全部任務都拿出來,依次執行
若是所有順利完成,則刪除剛纔取出的全部任務,等待下一次執行
若是中途出錯,則刪除已經完成的任務和出錯的任務,等待下次執行
若是第一個就出錯,則throw error
下面看一下應用場景(包含計算密集型操做,將其進行遞歸處理,而不阻塞進程):
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();
因爲js的回調異步特性,沒法經過try catch來捕捉全部的異常:
try { process.nextTick(function () { foo.bar(); }); } catch (err) { //can not catch it }
而對於web服務而言,實際上是很是但願這樣的:
//express風格的路由 app.get('/index', function (req, res) { try { //業務邏輯 } catch (err) { logger.error(err); res.statusCode = 500; return res.json({success: false, message: '服務器異常'}); } });
若是try catch可以捕獲全部的異常,這樣咱們能夠在代碼出現一些非預期的錯誤時,可以記錄下錯誤的同時,友好的給調用者返回一個500錯誤。惋惜,try catch沒法捕獲異步中的異常。
難道咱們就這樣放棄了麼? 其實還有一個辦法
咱們通常經過函數名傳遞的方式(引用的方式)將要執行的操做函數傳遞給onerror事件,如
window.onerror=reportError; window.onerror=function(){alert('error')}
但咱們可能不知道該事件觸發時還帶有三個默認的參數,他們分別是錯誤信息,錯誤頁面的url和錯誤行號。
<script type="text/javascript"> window.onerror=testError; function testError(){ arglen=arguments.length; var errorMsg="參數個數:"+arglen+"個"; for(var i=0;i<arglen;i++){ errorMsg+="\n參數"+(i+1)+":"+arguments[i]; } alert(errorMsg); window.onerror=null; return true; } function test(){ error } test() </script>
JavaScript中最多見的反模式作法是,回調內部再嵌套回調。
<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>
這裏定義了一個異步函數checkPassword,它觸發了另外一個異步函數db.query,然後者又可能觸發另一個異步函數hash。它能用,並且簡潔明瞭。可是,若是試圖向其添加新特性,它就會變得毛裏毛躁、險象環生,好比去處理那個數據庫錯誤,而不是拋出錯誤、記錄嘗試訪問數據庫的次數、阻塞訪問數據庫,等等。
下面咱們換一種寫法,雖然這種寫法很囉嗦可是可讀性更高並且更易擴展。
<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>
在平時寫嵌套時,咱們應該儘可能避免多層嵌套,否則中間某個地方出錯了將會致使你投入更多的時間去debug。