JavaScript自定義Promise實現(xiàn)流程
1. 初始結(jié)構(gòu)
我們先來回顧下 js 里 promise 實例是如何創(chuàng)建的:
let promsie = new Promise((resolve, reject) => { resolve('完成了') })
那這樣我們就知道了,在我們創(chuàng)建的 promise 類中的構(gòu)造函數(shù)要傳入的是一個函數(shù),而且我們在傳入?yún)?shù)的時候,這個函數(shù)參數(shù)會被自動執(zhí)行,接下來這個函數(shù)參數(shù)也有自己的參數(shù),也就是 resolve 和 reject 這兩個參數(shù),那我們也得讓我們手寫的 myPromise 支持這兩個參數(shù):
class myPromise { constructor(func) { func(resolve,reject) } }
這么寫顯然是有問題的,因為我們不知道在哪里調(diào)用 resolve 和 reject 這兩個參數(shù),因為我們還沒有定義這兩個對象,這兩個對象還是函數(shù),因此下面就要用類方法的形式創(chuàng)建這兩個函數(shù):
class myPromise { constructor(func) { func(resolve,reject) } resolve() {} reject() {} }
這樣是對的了么?
錯啦,因為我們得用 this 才能調(diào)用自身的方法,所以要在兩個參數(shù)前加上 this:
class myPromise { constructor(func) { func(this.resolve,this.reject) } resolve() {} reject() {} }
2. 定義狀態(tài)
promise 里有三種狀態(tài),分別是 pending,fulfilled 和 rejected,要注意狀態(tài)一經(jīng)改變就不能再改變了且只能由 pending 轉(zhuǎn)化為 fulfilled 或者 pending 轉(zhuǎn)化為 rejected。所以我們可以提前把這些狀態(tài)定義好,可以用 const 來創(chuàng)建外部的固定變量,然后將 pending 設(shè)置為默認(rèn)狀態(tài),這樣在每一個實例被創(chuàng)建以后就會有自身的狀態(tài)屬性可以進(jìn)行判斷和變動了:
class myPromise { static PENDING = '待定' static FULFILLED = '成功' static REJECTED = '拒絕' constructor(func) { this.status = myPromise.PENDING func(this.resolve,this.reject) } resolve() { if (this.status === myPromise.PENDING) { this.status = myPromise.FULFILLED } } reject() { if (this.status === myPromise.PENDING) { this.status = myPromise.REJECTED } } }
當(dāng)執(zhí)行 reolve 的時候,如果現(xiàn)在的狀態(tài)是待定,就讓他變?yōu)槌晒Γ?reject 同理。
并且,在執(zhí)行 resolve 或者 reject 的時候都是可以傳入一個參數(shù)的,這樣后面就能使用這個參數(shù)了:
class myPromise { static PENDING = '待定' static FULFILLED = '成功' static REJECTED = '拒絕' constructor(func) { this.status = myPromise.PENDING this.result = null func(this.resolve,this.reject) } resolve(result) { if (this.status === myPromise.PENDING) { this.status = myPromise.FULFILLED this.result = result } } reject(result) { if (this.status === myPromise.PENDING) { this.status = myPromise.REJECTED this.result = result } } }
那現(xiàn)在我們 new 一個 myPromise 的實例,大家覺得會不會成功呢?
3. this指向
承接上文,我們 new 了一個實例對象:
let promise = new myPromise((resolve,reject) => { resolve('小杰學(xué)前端') })
下面是輸出結(jié)果:
控制臺不出意外的報錯了,其實問題就出在這里:
我們在 resolve 中拿不到 status ,也就是 this 已經(jīng)跟丟了,這是為什么呢?
因為我們在 new 一個新實例的時候,執(zhí)行的是 constructor 里的內(nèi)容,也就是 constructoe 里的 this 指向的是實例對象,但是現(xiàn)在我們是在實例對象被創(chuàng)建后再在外部環(huán)境下執(zhí)行 resolve 方法的,這里的 resolve 看著像是和實例一起執(zhí)行的,其實不然。解決 class 的 this 指向問題一般會用箭頭函數(shù), bind 或者 proxy,這里我們就用 bind 來綁定
class myPromise { static PENDING = '待定' static FULFILLED = '成功' static REJECTED = '拒絕' constructor(func) { this.status = myPromise.PENDING this.result = null func(this.resolve.bind(this),this.reject.bind(this)) } resolve(result) { if (this.status === myPromise.PENDING) { this.status = myPromise.FULFILLED this.result = result } } reject(result) { if (this.status === myPromise.PENDING) { this.status = myPromise.REJECTED this.result = result } } }
這里就是給實例的 resolve 和 reject 方法綁定這個 this 為當(dāng)前的實例對象
或者我們也可以這樣解決:
class myPromise { static PENDING = '待定' static FULFILLED = '成功' static REJECTED = '拒絕' constructor(func) { this.status = myPromise.PENDING this.result = null let resolve = (result) => { if (this.status === myPromise.PENDING) { this.status = myPromise.FULFILLED this.result = result } } let reject = (result) => { if (this.status === myPromise.PENDING) { this.status = myPromise.REJECTED this.result = result } } func(resolve, reject) } }
4. then 方法
我們先回顧一下原生的 then 是怎么寫的:
let promise = new Promise((resolve, reject) => { resolve('完成了') reject('失敗了') }) promise.then( res => console.log(res), result => console.log(result))
那我們就開始寫我們的 then ,首先知道的是要傳遞兩個參數(shù),他們都是函數(shù)類型,一個表示狀態(tài)成功時的回調(diào),一個表示狀態(tài)失敗時的回調(diào):
class myPromise { static PENDING = '待定' static FULFILLED = '成功' static REJECTED = '拒絕' constructor(func) { this.status = myPromise.PENDING this.result = null func(this.resolve.bind(this),this.reject.bind(this)) } resolve(result) { if (this.status === myPromise.PENDING) { this.status = myPromise.FULFILLED this.result = result } } reject(result) { if (this.status === myPromise.PENDING) { this.status = myPromise.REJECTED this.result = result } } then(onFulfilled, onRejected) { if (this.status === myPromise.FULFILLED) { onFulfilled(this.result) } if (this.status === myPromise.REJECTED) { onRejected(this.result) } } }
這里 then 寫的也是淺顯易懂,我們測試一下能否正常輸出:
let promise = new myPromise((resolve,reject) => { resolve('成功了') reject('失敗了') } promise.then(res => console.log(res), result => console.log(result)) // 成功了
5. 執(zhí)行異常
如果我們在 new Promise 的時候,執(zhí)行函數(shù)里面我們拋出錯誤,是會觸發(fā)拒絕方法,可以把錯誤的信息作為內(nèi)容輸出出來:
let promise = new Promise((resolve, reject) => { throw new Error('拋出錯誤') }) promise.then( res => console.log(res), result => console.log(result)) //Error: 拋出錯誤
但是如果我們在 myPromise 這里寫上同樣的代碼則會報錯,于是我們就可以在執(zhí)行 resolve 和 reject 之前進(jìn)行判斷,可以用 try 和 catch 來該寫代碼,如果沒有報錯就正常執(zhí)行,有報錯就調(diào)用 reject 方法:
constructor(func) { this.status = myPromise.PENDING this.result = null try { func(this.resolve.bind(this),this.reject.bind(this)) } catch (error) { this.reject(error) } }
注意,在 catch 這里不需要用 bind ,因為這是立即調(diào)用的,只在內(nèi)部執(zhí)行的。
那到了這里我們的異常處理是不是和原生的差不多了呢,其實還沒有,我們知道在 then 里傳遞的是兩個函數(shù)參數(shù),那如果我們故意在原生代碼里不傳入函數(shù)作為參數(shù)呢:
let promise = new Promise((resolve, reject) => { resolve('成功了') }) promise.then( undefined, result => console.log(result))
實際上,這里什么也沒有輸出,但是如果我們以同樣的方法應(yīng)用在 myPromise 里會發(fā)生什么呢?
答案是會報一堆錯誤,這不是我們想要的,那我們只需要在執(zhí)行 then 里的參數(shù)前進(jìn)行類型判斷,如果它是函數(shù)就把原函數(shù)參數(shù)賦給它,否則就賦給它空函數(shù):
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => {} onRejected = typeof onRejected === 'function' ? onRejected : () => {} if (this.status === myPromise.FULFILLED) { onFulfilled(this.result) } if (this.status === myPromise.REJECTED) { onRejected(this.result) } }
現(xiàn)在再運(yùn)行就不會報錯了。
6. 支持異步(重頭戲1)
現(xiàn)在我們的手寫代碼里面還沒有植入異步功能,首先我們先了解一下原生 Promise 的一些運(yùn)行順序規(guī)則:
console.log(111) let promise = new Promise((resolve, reject) => { console.log(222) resolve('成功了') }) console.log(333) promise.then( res => console.log(res), result => console.log(result))
熟悉 JS 執(zhí)行機(jī)制和 prmoise 的同學(xué)一定能夠正確的判斷輸出的順序:
我們再看一下手寫的 myPromise 是如何輸出的:
console.log(111) let promise = new myPromise((resolve,reject) => { console.log(222) resolve('成功了') }) promise.then(res => console.log(res), result => console.log(result)) // 成功了 console.log(333)
執(zhí)行代碼,查看輸出:
那我們已經(jīng)找到問題的所在了,那就是 resolve 和 reject 里面都沒有設(shè)置異步執(zhí)行,二話不說直接套上定時器:
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => {} onRejected = typeof onRejected === 'function' ? onRejected : () => {} if (this.status === myPromise.FULFILLED) { setTimeout(() => { onFulfilled(this.result) }, 0); } if (this.status === myPromise.REJECTED) { setTimeout(() => { onRejected(this.result) }, 0); } }
現(xiàn)在我們的 then 里面就是異步執(zhí)行的了,再次運(yùn)行代碼查看輸出:
可以看到我們成功輸出了,但是現(xiàn)在異步的問題真的解決了嗎,現(xiàn)在又進(jìn)入另一個非常!非常!重要的重點了,我們先看一下原生 Promise 這段代碼:
console.log(111) let promise = new Promise((resolve, reject) => { console.log(222) setTimeout(() => { resolve('成功了') console.log(444) }, 0); }) console.log(333) promise.then( res => console.log(res), result => console.log(result))
這段代碼的輸出如下:
在 setTimeout 中會先執(zhí)行輸出444,再執(zhí)行 resolve ,我們再看一下同樣的代碼放在 myPromise 中的輸出結(jié)果:
并沒有輸出 “ 成功了 ”,這是為什么呢?
沒有輸出很可能的原因是 then 方法沒有被執(zhí)行,我們再各個位置輸出一下當(dāng)前的狀態(tài),進(jìn)行判斷:
console.log(111) let promise = new myPromise((resolve,reject) => { console.log(222) setTimeout(() => { console.log(myPromise.status) resolve('成功了') console.log(myPromise.status) console.log(444) }, 0); }) promise.then(res => { console.log(res); console.log(myPromise.status) }, result => console.log(result)) // 成功了 console.log(333)
看一下控制臺輸出:
可以看到在 setTimeout 里面的兩個狀態(tài)都被成功輸出了只有then 里的狀態(tài)并沒有成功輸出出來,那基本就可以判斷是 then 里的判斷出了問題,我們先分析一下,在執(zhí)行完 111,222,333后就開始處理異步,這里肯定是因為我們先執(zhí)行了 then ,然后在 then 里會判斷當(dāng)前狀態(tài)是成功就執(zhí)行成功的回調(diào),是失敗就執(zhí)行失敗的回調(diào),但是如果當(dāng)前狀態(tài)是 pending 呢?這不就出問題了嗎,在本題中 setTimeout 是宏任務(wù),要晚于微任務(wù) then 執(zhí)行,所以 then 執(zhí)行的時候還沒有改變狀態(tài),自然就不會輸出,那我們就在 then 里添加待定狀態(tài)的條件就可以了。
7. 回調(diào)保存(重頭戲2)
因為在函數(shù)體中執(zhí)行異步操作時,then 會先執(zhí)行,所以我們要在 then 里狀態(tài)為 pending ,也就是碰到異步操作的時候讓 then 里的函數(shù)稍后再執(zhí)行,等到狀態(tài)改變了再去執(zhí)行,那我們就定義兩個數(shù)組來保存回調(diào)
constructor(func) { this.status = myPromise.PENDING this.result = null this.resolveCallbacks = [] this.rejectCallbacks = [] try { func(this.resolve.bind(this),this.reject.bind(this)) } catch (error) { this.reject(error) } }
然后在 then 里狀態(tài)為 pending 時將函數(shù)保存:
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => {} onRejected = typeof onRejected === 'function' ? onRejected : () => {} if (this.status === myPromise.PENDING) { this.resolveCallbacks.push(() => { onFulfilled(this.result) }) this.rejectCallbacks.push(() => { onRejected(this.result) }) } if (this.status === myPromise.FULFILLED) { setTimeout(() => { onFulfilled(this.result) }, 0); } if (this.status === myPromise.REJECTED) { setTimeout(() => { onRejected(this.result) }, 0); } }
然后在執(zhí)行 resolve 和 reject 的時候,看看回調(diào)數(shù)組里有沒有待執(zhí)行的函數(shù),有的話就執(zhí)行:
resolve(result) { if (this.status === myPromise.PENDING) { this.status = myPromise.FULFILLED this.result = result this.resolveCallbacks.forEach(callback => { callback(result) }) } } reject(result) { if (this.status === myPromise.PENDING) { this.status = myPromise.REJECTED this.result = result this.rejectCallbacks.forEach(callback => { callback(result) }) } }
現(xiàn)在我們再運(yùn)行一下原來的測試代碼:
console.log(111) let promise = new myPromise((resolve,reject) => { console.log(222) setTimeout(() => { resolve('成功了') console.log(444) }, 0); }) promise.then(res => { console.log(res) }, result => console.log(result)) console.log(333)
現(xiàn)在輸出的是:
這個輸出結(jié)果也不是我們預(yù)期的呀,我們雖然成功輸出了執(zhí)行成功的回調(diào),但是應(yīng)該是在444后面輸出才對,其實這里還有個小坑,就是 resolve 和 reject 也得讓他倆變成異步執(zhí)行:
resolve(result) { setTimeout(() => { if (this.status === myPromise.PENDING) { this.status = myPromise.FULFILLED this.result = result this.resolveCallbacks.forEach(callback => { callback(result) }) } }, 0); } reject(result) { setTimeout(() => { if (this.status === myPromise.PENDING) { this.status = myPromise.REJECTED this.result = result this.rejectCallbacks.forEach(callback => { callback(result) }) } }, 0); }
執(zhí)行結(jié)果:
因為我們知道在 promise 的函數(shù)體內(nèi)是同步執(zhí)行的,promise.then 是微任務(wù),會異步執(zhí)行,所以在函數(shù)體內(nèi)碰到 resolve 時雖然狀態(tài)改變了但是并不會立刻輸出 result ,但是因為現(xiàn)在我們的函數(shù)體內(nèi)有異步代碼,此時我們執(zhí)行 then 只是把他的回調(diào)保存起來了,等到執(zhí)行回調(diào)的時候還得恢復(fù)原來的邏輯,也就是等函數(shù)體內(nèi)同步代碼輸出完了再執(zhí)行 then 輸出 resolve 里傳遞的值,所以要給 resolve 和 reject 加 setTimeout 讓他變成異步,不這樣的話在執(zhí)行 resolve 狀態(tài)改變了就會立刻輸出結(jié)果,這顯然不是我們期待的。
8. 重難點解讀
如果你之前還是云里霧里沒有懂的話,我們再次來解讀一下這段代碼:
console.log(111) let promise = new myPromise((resolve,reject) => { console.log(222) setTimeout(() => { resolve('成功了') console.log(444) }, 0); }) promise.then(res => { console.log(res) }, result => console.log(result)) console.log(333)
- 第一步,執(zhí)行 console.log(111),輸出 ”111“
- 第二步,創(chuàng)建 promise,同步執(zhí)行函數(shù)體內(nèi)的代碼,執(zhí)行 console.log(222),輸出 “222”
- 第三步,遇到 setTimeout,這是宏任務(wù),將他放到宏任務(wù)隊列,等待執(zhí)行
- 第四步,遇到 promise.then ,這是微任務(wù),將它放到微任務(wù)隊列,等待執(zhí)行
- 第五步,執(zhí)行 console.log(333) ,輸出 “333”
- 第六步,當(dāng)前執(zhí)行棧已經(jīng)清空,先執(zhí)行微任務(wù)隊列的任務(wù) promise.then ,發(fā)現(xiàn)當(dāng)前狀態(tài)還是待定,所以沒有輸出,沒有輸出的原因是改變狀態(tài)的 resolve 函數(shù)在 setTimeout 里,而我們目前還沒有執(zhí)行,然后將 then 的回調(diào)保存起來。
- 第七步,微任務(wù)隊列已經(jīng)清空,開始執(zhí)行宏任務(wù) setTimeout,因為 resolve 也是異步執(zhí)行的,所以放到異步隊列中,先執(zhí)行同步任務(wù) console.log(444),輸出 “444”
- 第八步,現(xiàn)在沒有要執(zhí)行的同步任務(wù)了,再執(zhí)行 resolve ,改變當(dāng)前狀態(tài)為成功,然后發(fā)現(xiàn)回調(diào)數(shù)組里有待執(zhí)行的函數(shù),執(zhí)行回調(diào)函數(shù),輸出結(jié)果 “成功了”
9. 鏈?zhǔn)焦δ埽ㄖ仡^戲3)
我們先了解一下 then 方法的特點,then方法中的成功回調(diào)或者失敗回調(diào),如果返回的是一個promise ,那么這個 promise 成功或者失敗的值就會傳遞給下一個 then 中。如果返回的是一個普通的不是 promise 的值,那就把這個值傳給下一個 then 。如果執(zhí)行時出現(xiàn)錯誤那就會執(zhí)行下一個 then 中的失敗的回調(diào)。
注意是參數(shù)函數(shù)的返回值,而不是then
本身的返回值,then
本身肯定是返回promise
的。
那么首先我們現(xiàn)在要做的就是拿到上一個回調(diào)中返回的值:
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => {} onRejected = typeof onRejected === 'function' ? onRejected : () => {} let newPromise = new myPromise((resolve, reject) => { if (this.status === myPromise.PENDING) { this.resolveCallbacks.push(() => { let res = onFulfilled() }) this.rejectCallbacks.push(() => { let res = onRejected() }) } if (this.status === myPromise.FULFILLED) { setTimeout(() => { let res = onFulfilled(this.result) }, 0); } if (this.status === myPromise.REJECTED) { setTimeout(() => { let res = onRejected(this.result) }, 0); } }) return newPromise }
then
能執(zhí)行的前提是,對應(yīng)的promise
執(zhí)行了resolve
,這樣能拿到resolve
的值。
所以后面的then
能拿到的前提是:前面的then
(返回值是promise
) 將參數(shù)函數(shù)返回值resolve
了。這里面略繞,得好好想想。所以我們現(xiàn)在需要執(zhí)行 resolve 并且把上一個回調(diào)傳進(jìn)來的值作為參數(shù):
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => {} onRejected = typeof onRejected === 'function' ? onRejected : () => {} let newPromise = new myPromise((resolve, reject) => { if (this.status === myPromise.PENDING) { this.resolveCallbacks.push(() => { let res = onFulfilled(this.result) resolve(res) }) this.rejectCallbacks.push(() => { let res = onRejected(this.result) resolve(res) }) } if (this.status === myPromise.FULFILLED) { setTimeout(() => { let res = onFulfilled(this.result) resolve(res) }, 0); } if (this.status === myPromise.REJECTED) { setTimeout(() => { let res = onRejected(this.result) resolve(res) }, 0); } }) return newPromise }
現(xiàn)在我們的工作完成了么?還沒有,準(zhǔn)確的說核心邏輯完成了一半,我們還沒有處理返回值是 promise 對象的情況,下面我們直接把這塊的邏輯抽象成一個函數(shù):
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => {} onRejected = typeof onRejected === 'function' ? onRejected : () => {} let newPromise = new myPromise((resolve, reject) => { if (this.status === myPromise.PENDING) { this.resolveCallbacks.push(() => { let res = onFulfilled(this.result) this.resolvePromise(res,resolve,reject) }) this.rejectCallbacks.push(() => { let res = onRejected(this.result) this.resolvePromise(res,resolve,reject) }) } if (this.status === myPromise.FULFILLED) { setTimeout(() => { let res = onFulfilled(this.result) this.resolvePromise(res,resolve,reject) }, 0); } if (this.status === myPromise.REJECTED) { setTimeout(() => { let res = onRejected(this.result) this.resolvePromise(res,resolve,reject) }, 0); } }) return newPromise } resolvePromise(res, resolve, reject) { if(res instanceof myPromise) { res.then(resolve, reject) } else{ // 普通值 resolve(res) } }
那么現(xiàn)在我們通過實例看一下能否實現(xiàn)鏈?zhǔn)秸{(diào)用,當(dāng)返回值是 promise 的時候:
let promise = new myPromise((resolve,reject) => { resolve(11) }) const a = new Promise((resolve,reject) => { resolve('ok') }) promise.then(res => { return a }).then(res => console.log(res))
運(yùn)行結(jié)果:
當(dāng)返回值是普通值的時候:
let promise = new myPromise((resolve,reject) => { resolve(11) }) promise.then(res => { return res }).then(res => console.log(res)) //11
可以看到現(xiàn)在我們的鏈?zhǔn)秸{(diào)用已經(jīng)基本實現(xiàn)了功能,其實這里面還有很多邊界情況沒有做處理,還實現(xiàn)的很粗糙,但是我們研究源碼要做的就是抽絲剝繭,關(guān)注最核心的邏輯就行,當(dāng)然能把邊界情況都處理了是最好的。
到此這篇關(guān)于JavaScript自定義Promise實現(xiàn)流程的文章就介紹到這了,更多相關(guān)JavaScript Promise內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
5個可以幫你理解JavaScript核心閉包和作用域的小例子
這篇文章主要介紹了5個可以幫你理解JavaScript核心閉包和作用域的小例子,本文是翻譯自國外的一篇文章,短小精悍,需要的朋友可以參考下2014-10-10Javascript的表單驗證-揭開正則表達(dá)式的面紗
Javascript的表單驗證-揭開正則表達(dá)式的面紗在本文重點介紹,感興趣的朋友一起學(xué)習(xí)吧2016-03-03JSP中使用JavaScript動態(tài)插入刪除輸入框?qū)崿F(xiàn)代碼
這篇文章主要介紹了JSP中如何使用JavaScript動態(tài)插入刪除輸入框,需要的朋友可以參考下2014-06-06JavaScript中統(tǒng)計Textarea字?jǐn)?shù)并提示還能輸入的字符
是在文本框中輸入文字的時候,會自動統(tǒng)計輸入的字符,并顯示用戶還能輸入的字符,其實js也可以實現(xiàn),下面就以示例的方式為大家講解下2014-06-06在TypeScript項目中搭配Axios封裝后端接口調(diào)用
這篇文章主要介紹了在TypeScript項目中搭配Axios封裝后端接口調(diào)用,本文記錄一下在?TypeScript?項目里封裝?axios?的過程,之前在開發(fā)?StarBlog-Admin?的時候已經(jīng)做了一次封裝,不過那時是JavaScript跟TypeScript還是有些區(qū)別的,需要的朋友可以參考下2024-01-01JavaScript通過RegExp實現(xiàn)客戶端驗證處理程序
通過RegExp實現(xiàn)客戶端驗:讓文本框只允許輸入數(shù)字、文本框只允許輸入中文、郵箱輸入格式的判斷等等,具體實現(xiàn)如下,感興趣的朋友可以參考下哈2013-05-05js循環(huán)map 獲取所有的key和value的實現(xiàn)代碼(json)
這篇文章主要介紹了js循環(huán)map 獲取所有的key和value的實現(xiàn)代碼(json),需要的朋友可以參考下2018-05-05