手寫 Promise() 相關函數:
Promise()、then()、catch()、finally()
// 定義三種狀態常量
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'class MyPromise {/*定義狀態和結果兩個私有屬性:1.使用 # 語法(ES2022+ 官方私有字段):在類中通過 # 前綴聲明屬性,該屬性僅在類的內部可訪問2.Symbol 作為屬性鍵:用 Symbol 作為屬性名,外部無法直接獲取 Symbol 引用。*/#state = PENDING // 狀態#result = undefined // 結果#thenables = [] // 存儲 then 方法回調的隊列constructor(executor) {// resolve 和 reject 主要功能即為改變狀態,設置結果const resolve = (value) => { // 解決時調用this.#changeState(FULFILLED, value)}const reject = (reason) => { // 拒絕時調用this.#changeState(REJECTED, reason)}try { // 只能捕獲同步錯誤,無法捕獲異步錯誤,如 setTimeout 里的錯誤executor(resolve, reject)} catch (err) {reject(err)}}// 改變狀態和設置結果的私有方法#changeState(state, result) {if (this.#state !== PENDING) returnthis.#state = statethis.#result = resultthis.#run()}// 處理 then 回調的私有方法#handleCallback(callback, resolve, reject) {if (typeof callback !== 'function') { // 狀態穿透,即 then 方法返回的 Promise 狀態與當前 Promise 狀態保持一致// then 回調是微任務,需要放到微任務隊列中執行queueMicrotask(() => { // 參考:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/queueMicrotaskconst settled = this.#state === FULFILLED ? resolve : rejectsettled(this.#result)})return}queueMicrotask(() => {try {const result = callback(this.#result)// 如果返回值是一個 Promise,則等待它完成后再 resolveif (result instanceof MyPromise) {result.then(resolve, reject)} else {resolve(result)}} catch (err) {reject(err)}})}/*執行隊列的私有方法,兩個執行時機:1.Promise 狀態改變時2.then 方法被調用時*/#run() {if (this.#state === PENDING) return// 取出隊列中的回調函數,依次執行(先進先出原則)while (this.#thenables.length) {const { onFulfilled, onRejected, resolve, reject } = this.#thenables.shift()try {if (this.#state === FULFILLED) { // 執行 onFulfilled 回調函數this.#handleCallback(onFulfilled, resolve, reject)} else { // 執行 onRejected 回調函數this.#handleCallback(onRejected, resolve, reject)}} catch (err) {reject(err)}}}/*onFulfilled 可選:一個在此 Promise 對象被兌現時異步執行的函數。它的返回值將成為 then() 返回的 Promise 對象的兌現值。onRejected 可選:一個在此 Promise 對象被拒絕時異步執行的函數。它的返回值將成為 catch() 返回的 Promise 對象的兌現值。*/then(onFulfilled, onRejected) {return new MyPromise((resolve, reject) => {// 將四個回調函數放入隊列,以便立即或將來處理this.#thenables.push({onFulfilled,onRejected,resolve,reject})// 啟動隊列處理this.#run()})}catch(onRejected) {return this.then(undefined, onRejected)}finally(onFinally) {this.then((value) => {MyPromise.resolve(onFinally()).then(() => value)},(reason) => {MyPromise.resolve(onFinally()).then(() => { throw reason })})}
}
驗證測試 MyPromise() 函數
const p1 = new Promise((resolve, reject) => {resolve(1)reject(2)
})
console.log('p1', p1)
const p2 = new Promise(() => { throw 123 })
console.log('p2', p2)
const p3 = new Promise((resolve, reject) => {setTimeout(() => {resolve('setTimeout') // 無法捕獲}, 0)
})
console.log('p3', p3)
const myP1 = new MyPromise((resolve, reject) => {resolve(1)reject(2)
})
console.log('myP1', myP1)
myP1.then((res) => {console.log('res', res) // 1return res + 1},(err) => {console.log('err', err) // 1}
).then((res) => {console.log('res', res) // 2// throw 'error'return res + 1
}).then((res) => {console.log('res', res) // 3
}).catch((err) => {console.log('err', err)
}).finally(() => {console.log('finally')
})
const myP2 = new MyPromise(() => { throw 123 })
console.log('myP2', myP2)
const myP3 = new MyPromise((resolve, reject) => {setTimeout(() => {resolve('setTimeout') // 無法捕獲}, 0)
})
console.log('myP3', myP3)