手寫 Promise Promise 構造函數 我們先來寫 Promise 構造函數的屬性和值,以及處理new Promise()時會傳入的兩個回調函數。如下: class myPromise { constructor(func) { this.state = 'pending' // Promi ...
目錄
手寫 Promise
Promise 構造函數
我們先來寫 Promise 構造函數的屬性和值,以及處理new Promise()
時會傳入的兩個回調函數。如下:
class myPromise {
constructor(func) {
this.state = 'pending' // Promise狀態
this.value = undefined // 成功的值
this.reason = undefined // 錯誤的值
this.resolveCallbacks = [] // 收集解決回調函數
this.rejectCallbacks = [] // 收集錯誤回調函數
try { // 對傳入的函數進行try...catch...做容錯處理
func(this.resolve, this.reject) // 執行傳入的兩個回調函數
} catch (e) {
this.reject(e)
}
}
}
三個狀態(pending、rejected和fulfilled)
pending:待定狀態。待定 Promise 。只有在then
方法執行後才會保持此狀態。
rejected:拒絕狀態。終止 Promise 。只有在reject
方法執行後才會由 pending 更改為此狀態。
fulfilled:解決狀態。終止 Promise 。只有在resolve
方法執行後才會由 pending 更改為此狀態。
註意:其中只有 pedding 狀態可以變更為 rejected 或 fulfilled 。rejected 或 fulfilled 不能更改其他任何狀態。
三個方法(resolve、reject和then)
resolve
方法實現要點
- 狀態由
pending
為fulfilled。
resolve
方法傳入的value
參數賦值給this.value
- 按順序執行
resolveCallbacks
裡面所有解決回調函數 - 利用
call
方法將解決回調函數內部的 this 綁定為undefined
坑點 1:resolve
方法內部 this 指向會丟失,進而造成this.value
丟失。
解決辦法:我們將resolve
方法定義為箭頭函數。在構造函數執行後,箭頭函數可以綁定實例對象的 this 指向。
// 2.1. Promise 狀態
resolve = (value) => { // 在執行構造函數時內部的this通過箭頭函數綁定實例對象
if (this.state === 'pending') {
this.state = 'fulfilled' // 第一點
this.value = value // 第二點
while (this.resolveCallbacks.length > 0) { // 第三點
this.resolveCallbacks.shift().call(undefined) // 第四點
}
}
}
reject
方法實現要點
- 狀態由
pending
為rejected
reject
方法傳入的reason
參數賦值給this.reason
- 按順序執行
rejectCallbacks
裡面所有拒絕回調函數 - 利用
call
方法將拒絕回調函數內部的 this 綁定為undefined
坑點 1: reject
方法內部 this 指向會丟失,進而造成this.reason
丟失。
解決辦法:我們將reject
方法定義為箭頭函數。在構造函數執行後,箭頭函數可以綁定實例對象的 this 指向。
// 2.1. Promise 狀態
reject = (reason) => { // 在執行構造函數時內部的this通過箭頭函數綁定實例對象
if (this.state === 'pending') {
this.state = 'rejected' // 第一點
this.reason = reason // 第二點
while (this.rejectCallbacks.length > 0) { // 第三點
this.rejectCallbacks.shift().call(undefined) // 第四點
}
}
}
then
方法實現要點
-
判斷then方法的兩個參數
onRejected
和onFulfilled
是否為function
。1.1
onRejected
和onFulfilled
都是function
,繼續執行下一步。1.2
onRejected
不是function
,將onRejected
賦值為箭頭函數,參數為reason
執行throw reason
1.3
onFulfilled
不是function
,將onFulfilled
賦值為箭頭函數,參數為value
執行return value
-
當前Promise狀態為rejected:
2.1
onRejected
方法傳入this.reason
參數,非同步執行。2.2 對執行的
onRejected
方法做容錯處理,catch
錯誤作為reject
方法參數執行。 -
當前Promise狀態為fulfilled:
3.1
onFulfilled
方法傳入this.value
參數,非同步執行。3.2 對執行的
onFulfilled
方法做容錯處理,catch
錯誤作為reject
方法參數執行。 -
當前Promise狀態為pending:
4.1 收集
onFulfilled
和onRejected
兩個回調函數分別push
給resolveCallbacks
和rejectCallbacks
。4.2 收集的回調函數同樣如上所述,先做非同步執行再做容錯處理。
-
返回一個 Promise 實例對象。
// 2.2. then 方法
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value // 第一點
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason } // 第一點
const p2 = new myPromise((resolve, reject) => {
if (this.state === 'rejected') { // 第二點
queueMicrotask(() => {
try {
onRejected(this.reason)
} catch (e) {
reject(e)
}
})
} else if (this.state === 'fulfilled') { // 第三點
queueMicrotask(() => {
try {
onFulfilled(this.value)
} catch (e) {
reject(e)
}
})
} else if (this.state === 'pending') { // 第四點
this.resolveCallbacks.push(() => {
queueMicrotask(() => {
try {
onFulfilled(this.value)
} catch (e) {
reject(e)
}
})
})
this.rejectCallbacks.push(() => {
queueMicrotask(() => {
try {
onRejected(this.reason)
} catch (e) {
reject(e)
}
})
})
}
})
return p2 // 第五點
}
Promise 解決程式(resolvePromise方法)
旁白:其實這個解決程式才是實現核心Promise最難的一部分。因為Promise A+規範對於這部分說的比較繞。
我們直擊其實現要點,能跑通所有官方用例就行。如下:
-
如果x和promise引用同一個對象:
1.1 調用
reject
方法,其參數為new TypeError()
-
如果x是一個promise或x是一個對象或函數:
2.1 定義一個
called
變數用於記錄then.call
參數中兩個回調函數的調用情況。2.2 定義一個
then
變數等於x.then
2.3
then
是一個函數。使用call
方法綁定x
對象,傳入解決回調函數和拒絕回調函數作為參數。同時利用called
變數記錄then.call
參數中兩個回調函數的調用情況。2.4
then
不是函數。調用resolve
方法解決Promise,其參數為x
2.5 對以上 2.2 檢索屬性和 2.3 調用方法的操作放在一起做容錯處理。
catch
錯誤作為reject
方法參數執行。同樣利用called
變數記錄then.call
參數中兩個回調函數的調用情況。 -
如果x都沒有出現以上兩種狀況:
調用
resolve
方法解決Promise,其參數為x
// 2.3 Promise解決程式
function resolvePromise(p2, x, resolve, reject) {
if (x === p2) {
// 2.3.1 如果promise和x引用同一個對象
reject(new TypeError())
} else if ((x !== null && typeof x === 'object') || typeof x === 'function') {
// 2.3.2 如果x是一個promise
// 2.3.3 如果x是一個對象或函數
let called
try {
let then = x.then // 檢索x.then屬性,做容錯處理
if (typeof then === 'function') {
then.call(x, // 使用call綁定會立即執行then方法,做容錯處理
(y) => { // y也可能是一個Promise,遞歸調用直到y被resolve或reject
if (called) { return }
called = true
resolvePromise(p2, y, resolve, reject)
},
(r) => {
if (called) { return }
called = true
reject(r)
}
)
} else {
resolve(x)
}
} catch (e) {
if (called) { return }
called = true
reject(e)
}
} else {
resolve(x)
}
}
called
變數的作用:記錄then.call
傳入參數(兩個回調函數)的調用情況。
根據Promise A+ 2.3.3.3.3規範:兩個參數作為函數第一次調用優先,以後的調用都會被忽略。
因此我們在以上兩個回調函數中這樣處理:
- 已經調用過一次:此時
called
已經為true,直接return
忽略 - 首次調用:此時
called
為undefined
,調用後called
設為true
註意:2.3 中的catch可能會發生(兩個回調函數)已經調用但出現錯誤的情況,因此同樣按上述說明處理。
運行官方測試用例
在完成上面的代碼後,我們最終整合如下:
class myPromise {
constructor(func) {
this.state = 'pending'
this.value = undefined
this.reason = undefined
this.resolveCallbacks = []
this.rejectCallbacks = []
try {
func(this.resolve, this.reject)
} catch (e) {
this.reject(e)
}
}
resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
while (this.resolveCallbacks.length > 0) {
this.resolveCallbacks.shift().call(undefined)
}
}
}
reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
while (this.rejectCallbacks.length > 0) {
this.rejectCallbacks.shift().call(undefined)
}
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
const p2 = new myPromise((resolve, reject) => {
if (this.state === 'rejected') {
queueMicrotask(() => {
try {
const x = onRejected(this.reason)
resolvePromise(p2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
} else if (this.state === 'fulfilled') {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(p2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
} else if (this.state === 'pending') {
this.resolveCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(p2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
this.rejectCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason)
resolvePromise(p2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
})
return p2
}
}
function resolvePromise(p2, x, resolve, reject) {
if (x === p2) {
reject(new TypeError())
} else if ((x !== null && typeof x === 'object') || typeof x === 'function') {
let called
try {
let then = x.then
if (typeof then === 'function') {
then.call(x,
(y) => {
if (called) { return }
called = true
resolvePromise(p2, y, resolve, reject)
},
(r) => {
if (called) { return }
called = true
reject(r)
}
)
} else {
resolve(x)
}
} catch (e) {
if (called) { return }
called = true
reject(e)
}
} else {
resolve(x)
}
}
// 新加入部分
myPromise.deferred = function () {
let result = {};
result.promise = new myPromise((resolve, reject) => {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
module.exports = myPromise;
新建一個文件夾,放入我們的 myPromise.js 併在終端執行以下命令:
npm init -y
npm install promises-aplus-tests
package.json 文件修改如下:
{
"name": "promise",
"version": "1.0.0",
"description": "",
"main": "myPromise.js",
"scripts": {
"test": "promises-aplus-tests myPromise"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"promises-aplus-tests": "^2.1.2"
}
}
開始測試我們的手寫 Promise,在終端執行以下命令即可:
npm test
Promise 其他方法補充
容錯處理方法
Promise.prototype.catch()
catch(onRejected) {
return this.then(undefined, onRejected)
}
Promise.prototype.finally()
finally(callback) {
return this.then(
value => {
return myPromise.resolve(callback()).then(() => value)
},
reason => {
return myPromise.resolve(callback()).then(() => { throw reason })
}
)
}
靜態方法
Promise.resolve()
static resolve(value) {
if (value instanceof myPromise) {
return value // 傳入的參數為Promise實例對象,直接返回
} else {
return new myPromise((resolve, reject) => {
resolve(value)
})
}
}
Promise.reject()
static reject(reason) {
return new myPromise((resolve, reject) => {
reject(reason)
})
}
Promise.all()
static all(promises) {
return new myPromise((resolve, reject) => {
let countPromise = 0 // 記錄傳入參數是否為Promise的次數
let countResolve = 0 // 記錄數組中每個Promise被解決次數
let result = [] // 存儲每個Promise的解決或拒絕的值
if (promises.length === 0) { // 傳入的參數是一個空的可迭代對象
resolve(promises)
}
promises.forEach((element, index) => {
if (element instanceof myPromise === false) { // 傳入的參數不包含任何 promise
++countPromise
if (countPromise === promises.length) {
queueMicrotask(() => {
resolve(promises)
})
}
} else {
element.then(
value => {
++countResolve
result[index] = value
if (countResolve === promises.length) {
resolve(result)
}
},
reason => {
reject(reason)
}
)
}
})
})
}
Promise.race()
static race(promises) {
return new myPromise((resolve, reject) => {
if (promises.length !== 0) {
promises.forEach(element => {
if (element instanceof myPromise === true)
element.then(
value => {
resolve(value)
},
reason => {
reject(reason)
}
)
})
}
})
}
上述所有實現代碼已放置我的Github倉庫。可自行下載測試,做更多優化。
[ https://github.com/chscript/myPromiseA- ]
參考