在js中有一個始終沒法繞過的問題,如何優雅地解決異步問題。實際上,js在執行過程當中,每遇到一個異步函數,都會將這個異步函數放入一個異步隊列中,只有當同步線程執行結束以後,纔會開始執行異步隊列中的函數,這個是討論解決異步方案的前提。javascript
主流的解決方法主要有如下幾種:vue
回調函數應該屬於最簡單粗暴的一種方式,主要表現爲在異步函數中將一個函數進行參數傳入,當異步執行完成以後執行該函數
java
事件監聽最經常使用的常見在於DOM元素事件綁定觸發,若是咱們想在DOM元素與用戶進行鼠標或其餘交互以後執行某些邏輯,就可使用事件監聽了jquery
引伸開來,咱們能夠建立一個Event類,定義on和emit方法來綁定和觸發自定義事件ajax
嗯,從寫法上來看,相比於回調函數,整潔了一些。編程
訂閱發佈模式定義了一種一對多的依賴關係,讓多個訂閱者對象同時監聽某發佈者對象。這個發佈者對象在自身狀態變化時,會通知全部訂閱者對象,使它們可以自動更新本身的狀態。vue就是基於發佈/訂閱者模式。
如何經過代碼實現一個發佈/訂閱者模式?
引用了《Pro JavaScript Design Patterns》第15章的例子promise
//建立一個主題發佈類 var Publisher=function(){ this.subscribers=[] } Publisher.prototype.publish=function(data){ this.subscribers.forEach(function(fn){ fn(data) }) } /* 在Function上掛載這個些方法,全部的函數均可以調用這些方法 表示全部函數均可以訂閱/取消訂閱相關的主題發佈 */ //訂閱 Function.prototype.subscribe=function(publisher){ var that=this; var isExist=publisher.subscribers.some(function(el){ if(el===that){ return true } }) if(!isExist){ publisher.subscribers.push(that) } //return this是爲了支持鏈式調用 return this } //取消訂閱 Function.prototype.unsubscribe=function(publisher){ var that=this; //就是將函數從發佈者的訂閱者列表中進行刪除 publisher.subscribers=publisher.subscribers.filter(function(el){ if(el!==that){ return true } }) return this } var publisher=new Publisher(); var subscriberObj=function(data){ console.log(data) } subscriberObj.subscribe(publisher)
這樣就實現了一個簡單的發佈訂閱者模式,每次發佈者發佈新內容時,就會調用publish方法,而後將內容做爲參數,依次調用訂閱者函數(subscribers)。
其實,發佈/訂閱模式與事件監聽很相似,異步
promise由社區最先提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。async
簡單來說,就仍是promise中有着三種狀態pending,fulfilled,rejected。在代碼中咱們能夠控制狀態的變動異步編程
建立一個Promise對象須要傳入一個函數,函數的參數是resolve和reject,在函數內部調用時,就分別表明狀態由pending=>fulfilled(成功),pending=>rejected(失敗)
一旦promise狀態發生變化以後,以後狀態就不會再變了。好比:調用resolve以後,狀態就變爲fulfilled,以後再調用reject,狀態也不會變化
Promise對象在建立以後會馬上執行,所以通常的作法是使用一個函數進行包裝,而後return一個promise對象
function timeout(){ return new Promise(function(resolve,reject){ ...//異步操做 }) }
在使用時能夠經過promise對象的內置方法then進行調用,then有兩個函數參數,分別表示promise對象中調用resolve和reject時執行的函數
function timeout(){ return new Promise(function(resolve,reject){ setTimeout(function(){ resolve(); },1000) }) } timeout() .then(function(){ ...//對應resolve時執行的邏輯 },function(){ ...//對應reject時執行的邏輯 })
可使用多個then來實現鏈式調用,then的函數參數中會默認返回promise對象
timeout()
.then(function(){ ...//對應resolve時執行的邏輯 },function(){ ...//對應reject時執行的邏輯 }) .then(function(){ ...//上一個then返回的promise對象對應resolve狀態時執行的邏輯 },function(){ ...//上一個then返回的promise對象對應reject狀態時執行的邏輯 })
使用promise來解決回調地獄的作法就是使用then的鏈式調用
function fnA(){ return new Promise(resolve=>{ ...//異步操做中resolve }) } function fnB(){ return new Promise(resolve=>{ ...//異步操做中resolve }) } function fnC(){ return new Promise(resolve=>{ ...//異步操做中resolve }) } fnA() .then(()=>{ return fnB() }) .then(()=>{ return fnC() })
清晰直觀了許多
建立一個generate函數很簡單
function* gen(){ yield 1 yield 2 return 3 }
區別於普通函數的地方在於function後面的*號,以及函數內部的yield。
*號是定義方式,帶有 * 號表示是一個generate函數,yield是其內部獨特的語法。
function* gen(){ yield 1 yield 2 return 3 } let g=gen(); console.log(g.next())//{value:1,done:false} console.log(g.next())//{value:2,done:false} console.log(g.next())//{value:3,done:true} console.log(g.next())//{value:undefined,done:true}
調用generate函數會生成一個遍歷器對象,不會當即執行,須要調用next執行,執行到帶有yield的那一步,next會返回一個對象,對象中value表示yield或return後的值,done表示函數是否已經執行結束(是否已經執行到return)。以後每次執行next都會從上一個yield開始繼續執行
function* gen(){ let res=yield 1 yield res return 3 } let g=gen(); console.log(g.next())//{value:1,done:false} console.log(g.next(333))//{value:333,done:false}
在next中傳入參數會做爲上一次yield的返回值(會忽略第一個next中傳遞的參數)
可是如何使用generate函數來進行異步編程?
這裏可使用ES2017中的async和await語法(其實屬於generate函數的語法糖)
使用async替換*號,使用await替換yield就將generate函數改形成了一個async函數
async function test(){ await 1 await asyncFn() } test()
其中await後面能夠跟promise和原始數據類型(至關與同步操做),而async返回一個promise對象(所以可使用then進行鏈式調用),使用時直接調用就好了
咱們再來看看如何解決回調地獄的問題
function fnA(){ return new Promise(resolve=>{ ...//異步操做中resolve }) } function fnB(){ return new Promise(resolve=>{ ...//異步操做中resolve }) } function fnC(){ return new Promise(resolve=>{ ...//異步操做中resolve }) } async function gen(){ let resA=await fnA() let resB=await fnB(resA) let resC=await fnC(resB) } gen()
相比於以前的方法更簡單直觀,也更容易理解總體的代碼流程。