javascript
JavaScript之手写Promise
實現一個簡易版 Promise
在完成符合 Promise/A+ 規范的代碼之前,我們可以先來實現一個簡易版 Promise,因為在面試中,如果你能實現出一個簡易版的 Promise 基本可以過關了。
那么我們先來搭建構建函數的大體框架
const PENDING = 'pending' const RESOLVED = 'resolved' const REJECTED = 'rejected'function MyPromise(fn) {const that = thisthat.state = PENDINGthat.value = nullthat.resolvedCallbacks = []that.rejectedCallbacks = []// 待完善 resolve 和 reject 函數// 待完善執行 fn 函數 }- 首先我們創建了三個常量用于表示狀態,對于經常使用的一些值都應該通過常量來管理,便于開發及后期維護
- 在函數體內部首先創建了常量 that,因為代碼可能會異步執行,用于獲取正確的 this 對象
- 一開始 Promise 的狀態應該是 pending
- value 變量用于保存 resolve 或者 reject 中傳入的值
- resolvedCallbacks 和 rejectedCallbacks 用于保存 then 中的回調,因為當執行完 Promise 時狀態可能還是等待中,這時候應該把 then 中的回調保存起來用于狀態改變時使用
接下來我們來完善 resolve 和 reject 函數,添加在 MyPromise 函數體內部
function resolve(value) {if (that.state === PENDING) {that.state = RESOLVEDthat.value = valuethat.resolvedCallbacks.map(cb => cb(that.value))} }function reject(value) {if (that.state === PENDING) {that.state = REJECTEDthat.value = valuethat.rejectedCallbacks.map(cb => cb(that.value))} }這兩個函數代碼類似,就一起解析了
- 首先兩個函數都得判斷當前狀態是否為等待中,因為規范規定只有等待態才可以改變狀態
- 將當前狀態更改為對應狀態,并且將傳入的值賦值給 value
- 遍歷回調數組并執行
完成以上兩個函數以后,我們就該實現如何執行 Promise 中傳入的函數了
try {fn(resolve, reject) } catch (e) {reject(e) }- 實現很簡單,執行傳入的參數并且將之前兩個函數當做參數傳進去
- 要注意的是,可能執行函數過程中會遇到錯誤,需要捕獲錯誤并且執行 reject 函數
最后我們來實現較為復雜的 then 函數
MyPromise.prototype.then = function(onFulfilled, onRejected) {const that = thisonFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => vonRejected =typeof onRejected === 'function'? onRejected: r => {throw r}if (that.state === PENDING) {that.resolvedCallbacks.push(onFulfilled)that.rejectedCallbacks.push(onRejected)}if (that.state === RESOLVED) {onFulfilled(that.value)}if (that.state === REJECTED) {onRejected(that.value)} }- 首先判斷兩個參數是否為函數類型,因為這兩個參數是可選參數
- 當參數不是函數類型時,需要創建一個函數賦值給對應的參數,同時也實現了透傳,比如如下代碼
- 接下來就是一系列判斷狀態的邏輯,當狀態不是等待態時,就去執行相對應的函數。如果狀態是等待態的話,就往回調函數中 push 函數,比如如下代碼就會進入等待態的邏輯
以上就是簡單版 Promise 實現
實現一個符合 Promise/A+ 規范的 Promise
接下來大部分代碼都是根據規范去實現的。我們先來改造一下 resolve 和 reject 函數
function resolve(value) {if (value instanceof MyPromise) {return value.then(resolve, reject)}setTimeout(() => {if (that.state === PENDING) {that.state = RESOLVEDthat.value = valuethat.resolvedCallbacks.map(cb => cb(that.value))}}, 0) } function reject(value) {setTimeout(() => {if (that.state === PENDING) {that.state = REJECTEDthat.value = valuethat.rejectedCallbacks.map(cb => cb(that.value))}}, 0) }- 對于 resolve 函數來說,首先需要判斷傳入的值是否為 Promise 類型
- 為了保證函數執行順序,需要將兩個函數體代碼使用 setTimeout 包裹起來
接下來繼續改造 then 函數中的代碼,首先我們需要新增一個變量 promise2,因為每個 then 函數都需要返回一個新的 Promise 對象,該變量用于保存新的返回對象,然后我們先來改造判斷等待態的邏輯
if (that.state === PENDING) {return (promise2 = new MyPromise((resolve, reject) => {that.resolvedCallbacks.push(() => {try {const x = onFulfilled(that.value)resolutionProcedure(promise2, x, resolve, reject)} catch (r) {reject(r)}})that.rejectedCallbacks.push(() => {try {const x = onRejected(that.value)resolutionProcedure(promise2, x, resolve, reject)} catch (r) {reject(r)}})})) }- 首先我們返回了一個新的 Promise 對象,并在 Promise 中傳入了一個函數
- 函數的基本邏輯還是和之前一樣,往回調數組中 push 函數
- 同樣,在執行函數的過程中可能會遇到錯誤,所以使用了 try...catch 包裹
- 規范規定,執行 onFulfilled 或者 onRejected 函數時會返回一個 x,并且執行 Promise 解決過程,這是為了不同的 Promise 都可以兼容使用,比如 JQuery 的 Promise 能兼容 ES6 的 Promise
接下來我們改造判斷執行態的邏輯
if (that.state === RESOLVED) {return (promise2 = new MyPromise((resolve, reject) => {setTimeout(() => {try {const x = onFulfilled(that.value)resolutionProcedure(promise2, x, resolve, reject)} catch (reason) {reject(reason)}})})) }- 其實大家可以發現這段代碼和判斷等待態的邏輯基本一致,無非是傳入的函數的函數體需要異步執行,這也是規范規定的
- 對于判斷拒絕態的邏輯這里就不一一贅述了,留給大家自己完成這個作業
最后,當然也是最難的一部分,也就是實現兼容多種 Promise 的 resolutionProcedure 函數
function resolutionProcedure(promise2, x, resolve, reject) {if (promise2 === x) {return reject(new TypeError('Error'))} }首先規范規定了 x 不能與 promise2 相等,這樣會發生循環引用的問題,比如如下代碼
let p = new MyPromise((resolve, reject) => {resolve(1) }) let p1 = p.then(value => {return p1 })然后需要判斷 x 的類型
if (x instanceof MyPromise) {x.then(function(value) {resolutionProcedure(promise2, value, resolve, reject)}, reject) }這里的代碼是完全按照規范實現的。如果 x 為 Promise 的話,需要判斷以下幾個情況:
當然以上這些是規范需要我們判斷的情況,實際上我們不判斷狀態也是可行的。
接下來我們繼續按照規范來實現剩余的代碼
let called = false if (x !== null && (typeof x === 'object' || typeof x === 'function')) {try {let then = x.thenif (typeof then === 'function') {then.call(x,y => {if (called) returncalled = trueresolutionProcedure(promise2, y, resolve, reject)},e => {if (called) returncalled = truereject(e)})} else {resolve(x)}} catch (e) {if (called) returncalled = truereject(e)} } else {resolve(x) }- 首先創建一個變量 called 用于判斷是否已經調用過函數
- 然后判斷 x 是否為對象或者函數,如果都不是的話,將 x 傳入 resolve 中
- 如果 x 是對象或者函數的話,先把 x.then 賦值給 then,然后判斷 then 的類型,如果不是函數類型的話,就將 x 傳入 resolve 中
- 如果 then 是函數類型的話,就將 x 作為函數的作用域 this 調用之,并且傳遞兩個回調函數作為參數,第一個參數叫做 resolvePromise ,第二個參數叫做 rejectPromise,兩個回調函數都需要判斷是否已經執行過函數,然后進行相應的邏輯
- 以上代碼在執行的過程中如果拋錯了,將錯誤傳入 reject 函數中
以上就是符合 Promise/A+ 規范的實現
總結
以上是生活随笔為你收集整理的JavaScript之手写Promise的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 搭建一个通用的脚手架
- 下一篇: 力扣(LeetCode)78