JS三座大山:原型原型鏈、做用域閉包、同步異步。
以前有寫過本身對閉包的理解,今天來總結一下JS中的異步。ajax
思考(案例來自stackoverflow):編程
function foo(){ var result; $ajax({ url:'...', success:function(response){ result=response; //return response;//tried this one as well } }); return result; } var result=foo();
初學異步的時候,這裏是很容易錯的地方,你想要獲取從服務器端返回的數據,結果卻一直undefined。
分析:
JavaScript是單線程語言,可是js中有不少任務耗時比較長,好比ajax請求,若是都按照順序進行,每每會出現瀏覽器無響應的狀況,因此就須要異步的形式。JS中全部的任務能夠分爲兩種:同步任務和異步任務。json
同步任務:在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;segmentfault
異步任務:不進入主線程,而進入任務隊列中的任務,只有任務隊列通知主線程,某個異步任務能夠執行了,這個任務纔會進入主線程執行。數組
事件循環(Event Loop):只有執行棧中的全部同步任務都執行完畢,系統纔會讀取任務隊列,看看裏面的異步任務哪些能夠執行,而後那些對應的異步任務,結束等待狀態,進入執行棧,開始執行。promise
異步的解決方案:瀏覽器
下面咱們嘗試將上面代碼改正一下,幾種方法以下:
1.callback服務器
function foo(callback){//定義函數的時候將另外一個函數(回調函數)做爲參數傳入定義的函數中。 $ajax({ //... success:callback//異步操做執行完畢後,再執行該回調函數,確保回調在異步操做以後執行。 }); } function myCallback(result){ //... } foo(myCallback);
回調函數自己是咱們約定俗成的一種叫法,咱們定義它,可是並不會本身去執行它,它最終被其餘人執行了。閉包
優勢:比較容易理解;
缺點:1.高耦合,維護困難,回調地獄;2.每一個任務只能指定一個回調函數;3.若是幾個異步操做之間並無順序之分,一樣也要等待上一個操做執行結束再進行下一個操做。下圖回調地獄(圖片來自於新浪微博(@ruanyf)):異步
2.Promise
function ajax(url){ return new Promise(function(resolve,reject){ var xhr=new XMLHttpRequest(); xhr.onload=function(){ resolve(this.responseText); }; xhr.onerror=reject; xhr.open('GET',url); xhr.send(); }); } ajax('/echo/json') .then(function(result){...}) .then(function(){...}) .catch(function(){...});
ES6給咱們提供了一個原生的構造函數Promise,Promise表明了一個異步操做,能夠將異步對象和回調函數脫離開來,經過.then方法在這個異步操做上綁定回調函數,Promise可讓咱們經過鏈式調用的方法去解決回調嵌套的問題,並且因爲promise.all這樣的方法存在,可讓同時執行多個操做變得簡單。
promise對象存在三種狀態:
1)Fulfilled:成功狀態
2)Rejected:失敗狀態
3)Pending:既不是成功也不是失敗狀態,能夠理解爲進行中狀態
promise對象的兩個重要方法:resolve/reject
1)resolve方法可使Promise對象的狀態改變爲成功,同時傳遞一個參數用於後續成功後的操做。
2)reject方法能夠將Promise對象的狀態改變爲失敗,同時將錯誤信息傳遞到後續錯誤處理的操做。
.then可使用鏈式調用,緣由在於:每一次執行該方法時總會返回一個Promise對象。
另外,在then的函數當中的返回值,能夠做爲後續操做的參數(例如:.then(return a).then(console.log(a+b)))
那麼問題來了,若是上面代碼異步操做拋出錯誤,會怎麼樣?會調用catch方法指定的回調函數,處理這個錯誤,並且then方法指定的回調函數,若是運行中拋出錯誤,也會被catch捕獲。Promise對象的錯誤具備「冒泡」性質,會一直向後傳遞,直到被捕獲爲止,也就是說,錯誤老是會被下一個catch語句捕獲。
理解Promise用法的關鍵點:
1.then方法是Promise實例的方法,即Promise.prototype上的,它的做用是爲Promise實例添加狀態改變時的回調函數,這個方法的第一個參數是resolved狀態的回調函數,第二個參數(可選)是rejected狀態的回調函數。
2.鏈式中的第二個then開始,它們的resolve中的參數,是前一個then中resolve的return語句的返回值。
3.關於執行順序:Promise在實例化的時候就會執行,也就是若是Promise的實例化語句中函數console.log輸出語句,它會比then中的先執行。Promise.all中傳入的Promise對象的數組(假設爲p一、p2),即便p2的運行速度比p1快,Promise.all方法仍然會按照數組中的順序將結果返回。
理解了上面這些方便寫原生的Promise,利用觀察者模式。後面補充。
Promise的缺點:
1.當處於未完成狀態時,沒法肯定目前處於哪一階段。
2.若是不設置回調函數,Promise內部的錯誤不會反映到外部。
3.沒法取消Promise,一旦新建它就會當即執行,沒法中途取消。
3.async/await:
不少人說async/await是異步編程的終極解決方案、
JavaScript 的 async/await 實現,離不開 Promise。
var superagent=require('superagent') function delay(){ return new Promise(function(resolve,reject){ setTimeout({ resolve(42); },3000); }) } async function getAllBooks(){ var bookIDs=await superagent.get('/user/books'); await delay(1000); return await superagent.get('/books/ids='JSON.stringify(bookIDs)); } getAllBooks() .then(function(){});
上面的 delay() 沒有申明爲 async。實際上,delay() 自己就是返回的 Promise 對象,加不加 async 結果都同樣。
只要在函數名以前加上async關鍵字,就代表這個函數內部有異步操做。這個異步操做返回一個Promise對象,前面用await關鍵字註明。函數執行的時候,一旦遇到await,就會先執行await後面的表達式中的內容(異步),再也不執行函數體後面的語句。等到異步操做執行完畢後,再自動返回到函數體內,繼續執行函數體後面的語句。
下面這段來自:https://segmentfault.com/a/11...
async:定義異步函數
1)自動把函數轉換爲Promise
2)當調用異步函數時,函數返回值會被resolve處理
3)異步函數內部可使用await
await:暫停異步函數的執行
1)當使用在Promise前面時,await等待Promise完成,並返回Promise的結果
2)await只能和Promise一塊兒使用,不能和callback一塊兒使用
3)await只能用在async函數中
async/await並不會取代promise,由於async/await底層依然使用promise。
async function getABC(){ let A = await getValueA(); // getValueA 花費 2 秒 let B = await getValueB(); // getValueA 花費 4 秒 let C = await getValueC(); // getValueA 花費 3 秒 return A*B*C }
每次遇到 await 關鍵字時,Promise 都會停下在,一直到運行結束,因此總共花費是 2+4+3 = 9 秒。await 把異步變成了同步。
async function getABC() { // Promise.all() 容許同時執行全部的異步函數 let results = await Promise.all([ getValueA, getValueB, getValueC ]); return results.reduce((total,value) => total * value); }
函數總耗時爲 4 秒(getValueB 的耗時)。
Async 的價值在於用寫同步的方式寫異步,1避免了阻塞,2必免寫回調
async/await詳細瞭解,推薦:https://segmentfault.com/a/11...