本文思路的開始是模擬實現Promise,因此先來探討Promise。vue
Promise 是異步編程的一種解決方案,最先是社區爲了避免在回調地獄裏沉淪而提出,ES6將其寫進了語言標準,統一了用法,原生提供了Promise對象。node
Promise 簡單說就是一個容器,裏面保存着一個異步操做結束後的結果。git
promise.then(data => console.log(data))
// then 表示異步操做完成
// data 就是結果
複製代碼
好比這裏有一個異步操做,用setTimeout模擬:github
let data = null
setTimeout(function() { data = 1 }, 1000)
複製代碼
當data發生改變後,我想「馬上」輸出。編程
第一種方法(顯而易見):api
setTimeout(function () {
let data = 1
console.log(data)
}, 1000)
複製代碼
第二種方法(略顯而易見):promise
setTimeout(function() {
let data = 1
setTimeout(() => console.log(data), 0)
}, 1000)
複製代碼
爲何第二種方式也能取到?瀏覽器
先來讀一遍教科書般(哪都能看到)的《JS事件循環機制》:bash
1.全部任務都在主線程上執行,造成一個執行棧。
2.主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
3.一旦"執行棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列"。那些對應的異步任務,結束等待狀態,進入執行棧並開始執行。
4.主線程不斷重複上面的第三步。
複製代碼
而後咱們來看第二種方法。異步
setTimeout(function() {
// 這個function內,對應一個執行棧
let data = 1 // 同步任務
setTimeout(() => console.log(data), 0) // 一個異步任務,執行到這時,會將該異步任務先放進"任務隊列"
// 同步任務 "let data = 1" 執行完,執行異步任務
}, 1000)
複製代碼
這裏調整兩行代碼順序,結果是同樣的。
setTimeout(function() {
setTimeout(() => console.log(data), 0)
let data = 1
}, 1000)
複製代碼
可是promise.then(data => console.log(data))
結構不太同樣,這裏用一個回調函數取得異步操做後的結果。
他是怎麼作到的。
// 簡易 Promise 定義
function Promise(excutor) {
this.callback = function() {}
let that = this
function resolve(value) {
// 放置一個異步任務,在異步任務執行回調
setTimeout(() => that.callback(value), 0)
}
excutor(resolve)
}
// then 方法只是保存callback函數
Promise.prototype.then = function (callback) {
this.callback = callback
}
const promise = new Promise(function(resolve) {
setTimeout(() => resolve(1), 1000)
})
promise.then(data => console.log(data))
複製代碼
先把data => console.log(data)
函數保存,再在resolve接收到異步數據後執行。
這裏能按照這樣的前後順序,跟上面第二種方法道理是同樣的。
setTimeout(function() {
setTimeout(() => console.log(data), 0) // => setTimeout(() => that.callback(value), 0) => 放置異步任務
let data = 1 // => promise.then(data => console.log(data)) => 都是同步任務
}, 1000)
複製代碼
這大體是promise的基本原理,以上咱們使用setTimeout來實現異步任務,從而達到模擬promise的效果。
在瀏覽器中,異步任務大體有:
宏任務 (MacroTask):setTimeout、setInterval、I/O、UI渲染
微任務 (MicroTask):Promise、MutationObsever
複製代碼
在node環境中,異步任務大體有:
宏任務 (MacroTask):setTimeout、setInterval、I/O、setImmediate
微任務 (MicroTask):Promise、process.nextTick
複製代碼
在一個執行棧中,會先執行同步代碼,遇到異步任務,會將其壓到"任務隊列"(task queue)中。
分別有"宏任務隊列"、"微任務隊列"。同步代碼執行完,將"微任務隊列"首任務的回調加入執行棧,執行。
循環"微任務隊列",直到隊列空。再循環"宏任務隊列"。
不一樣的"宏任務"、"微任務"之間還有優先級,會影響其執行順序。
回到Promise。
引擎裏實現的Promise,會建立一個"微任務"。而且提供了一些api,Promise會尊崇一些規範。
因此,咱們所說的模擬實現Promise,能夠這樣拆分。
異步任務咱們要看執行環境,有哪些可選。
這裏面最難理解的是Promise解決過程[[Resolve]](promise, x)。
Promise 解決過程是一個抽象的操做,其需輸入一個 promise 和一個值,
咱們表示爲 [[Resolve]](promise, x),若是 x 有 then 方法且看上去像一個 Promise ,
解決程序即嘗試使 promise 接受 x 的狀態;不然其用 x 的值來執行 promise 。
複製代碼
能夠看到Promise A+規範是很細節的,要想徹底經過他的測試,必須知足他的全部約束。
這裏有篇文章實現的挺好---Promise原理講解 && 實現一個Promise對象 (遵循Promise/A+規範)。
在他的resolve
方法裏能夠看到,是用setTimeout
來模擬。
其實最好是先用微任務模擬,若是環境不支持,再降級爲宏任務。
這個思路相似與vue
中的nextTick
,源碼傳送門。
能夠看到他的降級策略是:
Promise -> MutationObserver -> setImmediate -> setTimeout
複製代碼
nextTick
的做用是在數據渲染完成後執行,它的道理是在當前執行棧底放入一個異步任務。
相關參考資料: