Promise 是編寫異步的另外一種方式,鄙人愚見,它就是 Callback 的一種封裝git
相比 Callback ,它有如下特色github
決定一次異步有兩個環節異步
Promise 能夠給一個異步事件註冊多個處理函數,舉個栗子,就像這樣函數
let p1 = new Promise((resolve) => { fs.readFile('./test.js', 'utf8', (err, data) => { resolve(data) }) }) p1.then(data => console.log(data)) p1.then(data => console.log(data.toUpperCase()))
用 Callback 實現同樣的效果測試
就像這樣ui
let callbacks = [] function resolve(data){ callbacks.forEach(cb => cb(data)) } fs.readFile('./test.js', 'utf8', (err, data) => { resolve(data) }) callbacks.push(data => console.log(data)) callbacks.push(data => console.log(data.toUpperCase()))
將上述代碼封裝一下this
const fs = require('fs') class FakePromise { constructor(fn){ this.callbacks = [] resolve = resolve.bind(this) function resolve(data){ this.callbacks.forEach(cb => cb(data)) } fn(resolve) } then(onFulfilled){ this.callbacks.push(onFulfilled) } } let p1 = new FakePromise(resolve => { fs.readFile('./test.js', 'utf8', (err, data) => { resolve(data) }) }) p1.then(data => console.log(data)) p1.then(data => console.log(data.toUpperCase()))
哈?是否是和真的 Promise 有點像code
從發佈-訂閱模式的角度來看:對象
.then(onFulfilled)
來訂閱消息,註冊處理異步結果的函數resolve(data)
來發布消息,觸發處理異步結果的函數去執行,發佈的時機是異步事件完成時先前的代碼存在一個問題,若是在執行 p1.then(data => console.log(data))
以前,resolve(data)
就已經執行了,那麼再經過 .then(onFulfilled)
註冊的處理異步結果的函數將永遠不會執行隊列
爲了不這種狀況,改造 resolve 函數,在其內部添加 setTimeout,從而保證那些註冊的處理函數是在下一個事件隊列中執行,就像這樣
function resolve(value) { setTimeout(() => { this.callbacks.forEach(cb => cb(value)) }, 0) }
經過延時執行 resolve 內部的函數,保證了先訂閱消息,再發布消息
可是 Promise 還有個額外的功能是在發佈消息後,仍然能夠訂閱消息,而且當即執行,就像這樣
const fs = require('fs') let p1 = new Promise(resolve => { fs.readFile('./test.js', 'utf8', (err, data) => resolve(data)) }) p1.then(data => console.log(data)) setTimeout(function(){ p1.then(data => console.log(data.toUpperCase())) }, 5000)
5s以內,文件早已讀取成功,可是在5s以後,依然能夠經過 .then
註冊處理事件,而且該事件會當即執行
實現先發布,再訂閱的基礎是將消息保存下來。其次要記錄狀態,判斷消息是否已被髮布,若是未發佈消息,則經過 .then
來註冊回調時,是將回調函數添加到內部的回調隊列中;若是消息已發佈,則經過 .then
來註冊回調時,直接將消息傳至回調函數,並執行
Promise 規範中採用的狀態機制是 pending
、fulfilled
、rejected
pending
能夠轉化爲 fulfilled
或 rejected
,而且只能轉化一次。
轉化爲 fulfilled
和 rejected
後,狀態就不可再變
修改代碼以下
class FakePromise { constructor(fn) { this.value = null this.state = 'pending' this.callbacks = [] resolve = resolve.bind(this) function resolve(value) { setTimeout(() => { this.value = value this.state = 'fulfilled' this.callbacks.forEach(cb => cb(value)) }, 0) } fn(resolve) } then(onFulfilled) { if (this.state === 'pending') { this.callbacks.push(onFulfilled) } else { onFulfilled(this.value) } } }
既然實現了先發布,再訂閱,那麼 resolve 中的 setTimeout 是否是能夠去掉了?
並不能夠,由於人家正經的 Promise 是這樣的
let p1 = new Promise(resolve => { resolve('haha') }) p1.then(data => console.log(data)) p1.then(data => console.log(data.toUpperCase())) console.log('xixi') // xixi // haha // HAHA
只有保留 resolve 中 setTimeout 才能使 FakePromise 實現相同的效果
let p1 = new FakePromise(resolve => { resolve('haha') }) p1.then(data => console.log(data)) p1.then(data => console.log(data.toUpperCase())) console.log('xixi') // xixi // haha // HAHA
沒有 setTimeout 的輸出結果
// haha // HAHA // xixi
正經的 Promise 能夠鏈式調用,從而避免了回調地獄
let p1 = new Promise(resolve => { fs.readFile('./test.js', 'utf8', (err, data) => { resolve(data) }) }).then(res => { return new Promise(resolve => { fs.readFile('./main.js', 'utf8', (err, data) => { resolve(data) }) }) }).then(res => { console.log(res) })
正經的 Promise 調用 then 方法會返回一個新的 Promise 對象
咱們僞造的 FakePromise 並無實現這一功能,原來的 then 方法
... then(onFulfilled){ if (this.state === 'pending') { this.callbacks.push(onFulfilled) } else { onFulfilled(this.value) } } ...
原來的 then 方法就是根據 state 判斷是註冊 onFulfilled 函數,仍是執行 onFulfilled 函數
爲了實現 FakePromise 的高仿,咱們要改造 then 方法,使其返回一個新的 FakePromise ,爲了方便區分,將返回的 FakePromise 取名爲 SonFakePromise ,而先前調用 then 的對象爲 FatherFakePromise
那麼問題來了
首先,當構造一個新的 SonFakePromise 時,會將傳入的函數參數 fn 執行一遍,且這個函數有 resolve 參數
... then(onFulfilled){ if(this.state === 'pending'){ this.callbacks.push(onFulfilled) let SonFakePromise = new FakePromise(function fn(resolve){ }) return SonFakePromise }else{ onFulfilled(this.value) let SonFakePromise = new FakePromise(function fn(resolve){ }) return SonFakePromise } } ...
如今的問題是這個 SonFakePromise 何時 resolve ?即構造函數中的函數參數 fn 如何定義
結合正經 Promise 的例子來看
let faherPromise = new Promise(resolve => { fs.readFile('./test.js', 'utf8', (err, data) => { resolve(data) }) }).then(res => { return new Promise(resolve => { fs.readFile('./main.js', 'utf8', (err, data) => { resolve(data) }) }) }).then(res => { console.log(res) }) // 等同於 let faherPromise = new Promise(resolve => { fs.readFile('./test.js', 'utf8', (err, data) => { resolve(data) }) }) let sonPromise = faherPromise.then(function onFulfilled(res){ return new Promise(function fn(resolve){ fs.readFile('./main.js', 'utf8', (err, data) => { resolve(data) }) }) }).then(res => { console.log(res) })
在例子中,onFulfilled 函數以下,且其執行後返回一個新的 Promise,暫時取名爲 fulPromise
function onFulfilled(res) { return new Promise(function fn(resolve){ fs.readFile('./main.js', 'utf8', (err, data) => { resolve(data) }) }) }
如今來分析一下,fatherPromise,sonPromise 和 fulPromise 這三者的關係
但願下面的代碼能有助於理解
let fatherPromise = new Promise(function fatherFn(fatherResolve){ fs.readFile('./test.js', 'utf8', (err, data) => { fatherResolve(data) }) }) let sonPromise = fatherPromise.then(retFulPromise) function retFulPromise(res) { let fulPromise = new Promise(function fulFn(fulResolve){ fs.readFile('./main.js', 'utf8', (err, data) => { fulResolve(data) }) }) return fulPromise }
fatherPromise 的狀態爲 fulfilled 時,會執行 retFulPromise,其返回 fulPromise ,當這個 fulPromise 執行 fulResolve 時,即完成讀取 main.js 時, sonPromise 也會執行內部的 resolve
因此能夠當作,sonPromise 的 sonResolve 函數,也被註冊到了 fulPromise 上
So,瞭解了整個流程,該怎麼修改本身的 FakePromise 呢?
秀操做,考驗技巧的時候到了,將 sonResolve 的引用保存起來,註冊到 fulFakePromise 上
const fs = require('fs') class FakePromise { constructor(fn) { this.value = null this.state = 'pending' this.callbacks = [] resolve = resolve.bind(this) function resolve(value) { setTimeout(() => { this.value = value this.state = 'fulfilled' this.callbacks.forEach(cb => { let returnValue = cb.onFulfilled(value) if (returnValue instanceof FakePromise) { returnValue.then(cb.sonResolveRes) } }) }) } fn(resolve) } then(onFulfilled) { if (this.state === 'pending') { let sonResolveRes = null let sonFakePromise = new FakePromise(function sonFn(sonResolve) { sonResolveRes = sonResolve }) this.callbacks.push({ sonFakePromise, sonResolveRes, onFulfilled }) return sonFakePromise } else { let value = onFulfilled(this.value) let sonResolveRes = null let sonFakePromise = new FakePromise(function sonFn(sonResolve) { sonResolveRes = sonResolve }) if (value instanceof FakePromise) { value.then(sonResolveRes) } return sonFakePromise } } }
let fatherFakePromise = new FakePromise(resolve => { fs.readFile('./test.js', 'utf8', (err, data) => { resolve(data) }) }) let sonFakePromise = fatherFakePromise.then(function onFulfilled(res) { return new FakePromise(function fn(resolve) { fs.readFile('./main.js', 'utf8', (err, data) => { resolve(data) }) }) }).then(res => { console.log(res) })
let fatherFakePromise = new FakePromise(resolve => { fs.readFile('./test.js', 'utf8', (err, data) => { resolve(data) }) }) setTimeout(function () { let sonFakePromise = fatherFakePromise.then(function onFulfilled(res) { return new FakePromise(function fn(resolve) { fs.readFile('./main.js', 'utf8', (err, data) => { resolve(data) }) }) }).then(res => { console.log(res) }) }, 1000)
let fatherFakePromise = new FakePromise(resolve => { resolve('haha') }) let sonFakePromise = fatherFakePromise.then(function onFulfilled(res) { return new FakePromise(function fn(resolve) { fs.readFile('./main.js', 'utf8', (err, data) => { resolve(data) }) }) }).then(res => { console.log(res) })
let fatherFakePromise = new FakePromise(resolve => { resolve('haha') }) setTimeout(function () { let sonFakePromise = fatherFakePromise.then(function onFulfilled(res) { return new FakePromise(function fn(resolve) { fs.readFile('./main.js', 'utf8', (err, data) => { resolve(data) }) }) }).then(res => { console.log(res) }) }, 1000)