Javascript的優勢之一是其如何處理異步代碼。異步代碼會被放入一個事件隊列,等到所有其他代碼執行后才進行,而不會阻塞線程
1 理解異步代碼:
1.1 JavaScript最基礎的異步函數是setTimeout和setInterval。setTimeout會在一定時間后執行給定的函數。它接受一個回調函數作為第一參數和一個毫秒時間作為第二參數。
console.log(1); setTimeout(function() { console.log('a'); },1000); setTimeout(function() { console.log('b'); },1000); setTimeout(function() { console.log('c'); },1000); console.log(2);
正如預期,控制台先輸出1、2,大約500毫秒后,再看到“a”、“b”、“c”。我用“大約”是因為setTimeout事實上是不可預知的。實際上,甚至 HTML5規范都提到了這個問題:
“這個API不能保證計時會如期准確地運行。由於CPU負載、其他任務等所導致的延遲是可以預料到的。”
1.2 Event Loop隊列
有趣的是,直到在同一程序段中所有其余的代碼執行結束后,超時才會發生。所以如果設置了超時,同時執行了需長時間運行的函數,那么在該函數執行完成之前,超時甚至都不會啟動。實際上,異步函數,如setTimeout和setInterval,被壓入了稱之為Event Loop的隊列。
Event Loop是一個回調函數隊列。當異步函數執行時,回調函數會被壓入這個隊列。JavaScript引擎直到異步函數執行完成后,才會開始處理事件循環。這意味着JavaScript代碼不是多線程的,即使表現的行為相似。事件循環是一個先進先出(FIFO)隊列,這說明回調是按照它們被加入隊列的順序執行的。
2 JQuery異步處理
異步Javascript與XML(AJAX)永久性的改變了Javascript語言的狀況。突然間,瀏覽器不再需要重新加載即可更新web頁面。 在不同的瀏覽器中實現Ajax的代碼可能漫長並且乏味. 但是有些地方還是需要注意
var data; $.ajax({ url: "some/url/1", success: function( data ) { // 放在jquery指定的success函數里面可以保證異步請求完成 console.log( data ); } }) // 這里並不能獲取數據 ajax異步請求還未完成 console.log( data );
容易犯的錯誤,是在調用$.ajax之后馬上使用data,但是實際上是這樣的
xmlhttp.open( "GET", "some/ur/1", true ); xmlhttp.onreadystatechange = function( data ) { if ( xmlhttp.readyState === 4 ) { console.log( data ); } }; xmlhttp.send( null ); 底層的XmlHttpRequest對象發起請求,設置回調函數用來處理XHR的readystatechnage事件。
然后執行XHR的send方法。在XHR運行中,當其屬性readyState改變時readystatechange事件就會被觸發,
只有在XHR從遠端服務器接收響應結束時回調函數才會觸發執行。
3 回調函數--處理異步
異步編程很容易陷入我們常說的“回調地獄”。因為事實上幾乎JS中的所有異步函數都用到了回調,連續執行幾個異步函數的結果就是層層嵌套的回調函數以及隨之而來的復雜代碼。
eg: Nodejs中常見異步函數
var fs = require( "fs" ); fs.exists( "index.js", function() { // 回調函數處理異步 fs.readFile( "index.js", "utf8", function( err, contents ) { // 回調函數處理異步 contents = someFunction( contents ); // do something with contents fs.writeFile( "index.js", "utf8", function() { // 回調函數處理異步 console.log( "全部按順序執行完" ); }); }); }); console.log( "executing..." );
3.1 清除嵌套回調
3.1.1 命名函數避免雙層嵌套,解決嵌套回調
清除嵌套回調的一個便捷的解決方案是簡單的避免雙層以上的嵌套。傳遞一個命名函數給作為回調參數,而不是傳遞匿名函數:
4 事件--處理異步
事件是另一種當異步回調完成處理后的通訊方式。一個對象可以成為發射器並派發事件,而另外的對象則監聽這些事件。這種類型的事件處理方式稱之為 觀察者模式
5 Promise(ES6)--處理異步
// 實例化 Promise對象 let time = function(time) { return new Promise((resolve, reject) => { console.log('Promise實例化完成'); // Promise實例化后立即執行 if (time >= 3000) { setTimeout(function(){ resolve('大於3秒的時間后才顯示出來'); }, time) } else { reject('時間不能小於3秒') } }); }; // 實例化Promise對象后才能使用 then time(1000).then((value) => { // 1000 會走第二條判斷Reject(Pending => Reject 失敗會走第二個回調) console.log(value); }, (error) => { console.log(error); }); console.log(1); // 'Promise實例化完成' 1 '時間不能小於3秒'
6 ES7的Async/Await--處理異步
var sleep = function (time) { return new Promise(function(resolve, reject) { // 返回一個promise對象 setTimeout(function() { resolve(); }, time) }) }; var start = async function() { console.log('開始'); await sleep(3000); // 等待異步過程完成再往下執行 console.log('結束'); } start(); // 控制台先輸出start,稍等3秒后,輸出了end。
基本規則
async 表示這是一個async(異步)函數,await只能用在這個函數里面。
await 表示在這里等待promise返回結果了,再繼續執行。
await 后面跟着的應該是一個promise對象(當然,其他返回值也沒關系,只是會立即執行,不過那樣就沒有意義了…)
獲得返回值 await等待的雖然是promise對象,但不必寫.then(..),直接可以得到返回值。 var sleep = function (time) { return new Promise(function (resolve, reject) { setTimeout(function () { // 返回 ‘ok’ resolve('ok'); }, time); }) }; var start = async function () { let result = await sleep(3000); console.log(result); // 收到 ‘ok’ };
// 獲取異步結果 let sleep = function (time) { return new Promise(function(resolve, reject){ setTimeout(function() { resolve('異步返回結果'); }, time) }); }; let start = async function() { let result = await sleep('3000'); console.log(result); }; start(); // 3秒后輸出 '異步返回結果'
// ** 捕獲錯誤 var asyncFn = function (time) { return new Promise((resolve, reject) => { setTimeout(function() { reject('異步出了問題'); }, time) }); }; var start = async function() { console.log('開始'); let result = await asyncFn(3000); // 返回了一個錯誤,不會往下執行了 console.log(result); console.log('結束'); }; // 開始 // (node:2200) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): 異步出了問題 // (node:2200) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. // 既然.then(..)不用寫了,那么.catch(..)也不用寫,可以直接用標准的try catch語法捕捉錯誤。 var start = async function() { try{ console.log('開始'); let result = await asyncFn(3000); // 返回了一個錯誤,不會往下執行了 console.log(result); console.log('結束'); } catch(err) { //出了錯誤 console.log(err) } }; // 開始 // 異步出了問題 start();
參考資料
http://cnodejs.org/topic/5640b80d3a6aa72c5e0030b6
問題:
1 Promise是不是還是使用回調函數方式處理異步,但是避免了多重回調嵌套的問題?
An: Promise是處理異步的一種方式和回調函數處理異步方式是平行關系