上一篇文章介紹了js異步的底層基礎--Event Loop模型,本文將介紹JS中傳統的幾種異步操做實現的模式。jquery
回調函數是異步的最基本實現方式。es6
// 例子:回調函數 const f1 = (callback) => setTimeout(()=>{ console.log('f1') // 自身要執行的函數內容 callback() },1000) const f2 = () =>{ console.log('f2') } f1(f2)
缺點:ajax
fs.readFile
等函數,只提供傳入一個回調函數,若是想觸發2個回調函數,就只能再用一個函數把這兩個函數包起來// 例子1:回調地獄,依次執行f1,f2,f3... const f1 = (callback) => setTimeout(()=>{ console.log('f1') callback() },1000) const f2 = (callback) =>setTimeout(()=>{ console.log('f2') callback() },1000) ... // 假設還有f3,f4...fn都是相似的函數,那麼就要不斷的把每一個函數寫成相似的形式,而後使用下面的形式調用: f1(f2(f3(f4))) // 例子2:若是想給`fs.readFile`執行2個回調函數callback1,callback2 // 必須先包起來 const callback3 = ()=>{ callback1 callback2 } fs.readFile(filename,[encoding],callback3)
事件監聽的含義是:採用事件驅動模式,讓任務的執行不取決於代碼的順序,而取決於某個事件是否發生。先給出實現的效果:編程
const f1 = () => setTimeout(()=>{ console.log('f1') // 函數體 f1.trigger('done') // 執行完函數體部分 觸發done事件 },1000) f1.on('done',f2) // 綁定done事件回調函數 f1() // 一秒後輸出 f1,再過一秒後輸出f2
接下來手動實現一下上面的例子,體會一下這種方案的原理:設計模式
const f1 = () => setTimeout(()=>{ console.log('f1') // 函數體 f1.trigger('done') // 執行完函數體部分 觸發done事件 },1000) /*----------------核心代碼start--------------------------------*/ // listeners 用於存儲f1函數各類各樣的事件類型和對應的處理函數 f1.listeners = {} // on方法用於綁定監聽函數,type表示監聽的事件類型,callback表示對應的處理函數 f1.on = function (type,callback){ if(!this.listeners[type]){ this.listeners[type] = [] } this.listeners[type].push(callback) //用數組存放 由於一個事件可能綁定多個監聽函數 } // trigger方法用於觸發監聽函數 type表示監聽的事件類型 f1.trigger = function (type){ if(this.listeners&&this.listeners[type]){ // 依次執行綁定的函數 for(let i = 0;i < this.listeners[type].length;i++){ const fn = this.listeners[type][i] fn() } } } /*----------------核心代碼end--------------------------------*/ const f2 = () =>setTimeout(()=>{ console.log('f2') },1000) const f3 = () =>{ console.log('f3') } f1.on('done',f2) // 綁定done事件回調函數 f1.on('done',f3) // 多個回調 f1() // 一秒後輸出 f1, f3,再一秒後輸出f2
核心原理:數組
listeners
對象儲存要監聽的事件類型和對應的函數;on
方法時,往listeners
中對應的事件類型添加回調函數;trigger
方法時,檢查listeners
中對應的事件,若是存在回調函數,則依次執行;和回調相比,代碼上的區別只是把原先執行callback
的地方,換成了執行對應監聽事件的回調函數。可是從模式上看,變成了事件驅動模型。promise
在剛剛事件監聽的例子中,咱們改造了f1,使它擁有了添加監聽函數和觸發事件的功能,若是咱們把這部分功能移到另一個全局對象上實現,就成了發佈訂閱者模式:異步
// 消息中心對象 const Message = { listeners:{} } // subscribe方法用於添加訂閱者 相似事件監聽中的on方法 裏面的代碼徹底一致 Message.subscribe = function (type,callback){ if(!this.listeners[type]){ this.listeners[type] = [] } this.listeners[type].push(callback) //用數組存放 由於一個事件可能綁定多個監聽函數 } // publish方法用於通知消息中心發佈特定的消息 相似事件監聽中的trigger 裏面的代碼徹底一致 Message.publish = function (type){ if(this.listeners&&this.listeners[type]){ // 依次執行綁定的函數 for(let i = 0;i < this.listeners[type].length;i++){ const fn = this.listeners[type][i] fn() } } } const f2 = () =>setTimeout(()=>{ console.log('f2') },1000) const f3 = () => console.log('f3') Message.subscribe('done',f2) // f2函數 訂閱了done信號 Message.subscribe('done',f3) // f3函數 訂閱了done信號 const f1 = () => setTimeout(()=>{ console.log('f1') Message.publish('done') // 消息中心發出done信號 },1000) f1() // 執行結果和上面徹底同樣
若是認真看的話會發現,這裏的代碼和上一個例子幾乎沒有區別,僅僅是:async
on
方法更名爲subscribe
方法,而且移到Message對象上trigger
方法更名爲publish
,而且移到Message對象上這麼作有意義嗎?固然有。異步編程
如圖:
消息中心的做用正如它的名字--承擔了消息中轉的功能,全部發布者和訂閱器都只和它進行消息傳遞。有這個對象的存在,能夠更方便的查看全局的消息訂閱狀況。
實質上,這也是設計模式中,觀察者模式和發佈/訂閱者模式的區別。
Promise 是異步編程的一種解決方案,它由社區最先提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。
注意,只是在es6原生提供了Promise對象,不表明Promise的設計是在es6纔出現的。最典型的,當咱們還在使用jquery
的$.ajax
時,已經使用$.ajax().then().catch()
時,就已經用到了Promise對象。所以這個也歸爲傳統異步實現。
關於Promise詳細內容,建議你們學習阮一峯老師的ES6教程,本文只介紹異步相關的核心內容。
接下來一樣地,用js模擬實現一個簡單的Promise對象。
首先分析Promise
的要點:
resolve(reject)
方法resolve
和reject
方法改變狀態:resolve使狀態從pending(進行中)
變成、fulfilled(已成功)
;reject使狀態變成rejected(已失敗)
then
方法用於註冊回調函數,而且返回值必須爲Promise對象,這樣才能實現鏈式調用(鏈式調用是指p.then().then().then()這樣的形式
)根據上述分析,實現一個有then
和resolve
方法的簡單Promise
對象:
// 例子:手動實現簡單Promise function MyPromise(fn){ this.status = 'pending' this.resolves =[] //存放成功執行後的回調函數 return fn(this.resolve.bind(this))// 這裏必須bind,不然this對象會根據執行上下文改變 } // then方法用於添加註冊回調函數 MyPromise.prototype.then = function(fn){ // 註冊回調函數 並返回Promise. this.resolves.push(fn) return this } // resolve用於變動狀態 而且觸發回調函數,實際上resolve能夠接受參數 這裏簡單實現就先忽略 MyPromise.prototype.resolve = function(){ this.status = 'fulfilled' if(this.resolves.length===0){ return } // 依次執行回調函數 並清空 for(i=0;i<this.resolves.length;i++){ const fn = this.resolves[i] fn() } this.resolves = [] //清空 return this } // 使用寫好的MyPromise作實驗 const f1 = new MyPromise(resolve=>{ setTimeout(()=>{ console.log('f1 開始運行') resolve() },1000) }) f1.then(()=>{ setTimeout(()=>{ console.log('f1的第一個then') },3000) }) // 一個小思考,下面函數的執行輸出是什麼? f1.then(()=>{ setTimeout(()=>{ console.log('f1的第一個then') },3000) }).then(()=>{ setTimeout(()=>{ console.log('f1的第二個then') },1000) })
以上就是Promise的核心思路。
本文針對傳統的幾種異步實現方案作了說明。而ES6中新的異步處理方案Generator
和async/await
會在後面補充。
若是以爲寫得很差/有錯誤/表述不明確,都歡迎指出
若是有幫助,歡迎點贊和收藏,轉載請徵得贊成後著明出處。若是有問題也歡迎私信交流,主頁有郵箱地址
若是以爲做者很辛苦,也歡迎打賞一杯咖啡~