看了就會,手寫Promise原理,最通俗易懂的版本!!!

前言

你們好,我是林三心,相信你們在平常開發中都用過Promise,我一直有個夢想,就是以最通俗的話,講最複雜的知識,因此我把通俗易懂放在了首位,今天就帶你們手寫實現如下Promise吧,相信你們一看就懂。面試

image.png

resolve和reject

我們來看一段Promise的代碼:數組

let p1 = new Promise((resolve, reject) => {
    resolve('成功')
    reject('失敗')
})
console.log('p1', p1)

let p2 = new Promise((resolve, reject) => {
    reject('失敗')
    resolve('成功')
})
console.log('p2', p2)

let p3 = new Promise((resolve, reject) => {
    throw('報錯')
})
console.log('p3', p3)

複製代碼

那麼會輸出什麼呢?請看:promise

截屏2021-08-01 上午11.53.33.png

這裏暴露出了四個知識點:markdown

  • 一、執行了resolve,Promise狀態會變成fulfilled
  • 二、執行了reject,Promise狀態會變成rejected
  • 三、Promise只以第一次爲準,第一次成功就永久fulfilled,第一次失敗就永遠狀態爲rejected
  • 四、Promise中有throw的話,就至關於執行了reject

那麼我們就把這四個知識點一步步實現吧!!!app

一、實現resolve與reject

你們要注意:Promise的初始狀態是pending異步

這裏很重要的一步是resolve和reject的綁定this,爲何要綁定this呢?這是爲了resolve和reject的this指向永遠指向當前的MyPromise實例,防止隨着函數執行環境的改變而改變ide

class MyPromise {
    // 構造方法
    constructor(executor) {

        // 初始化值
        this.initValue()
        // 初始化this指向
        this.initBind()
        // 執行傳進來的函數
        executor(this.resolve, this.reject)
    }

    initBind() {
        // 初始化this
        this.resolve = this.resolve.bind(this)
        this.reject = this.reject.bind(this)
    }

    initValue() {
        // 初始化值
        this.PromiseResult = null // 終值
        this.PromiseState = 'pending' // 狀態
    }

    resolve(value) {
        // 若是執行resolve,狀態變爲fulfilled
        this.PromiseState = 'fulfilled'
        // 終值爲傳進來的值
        this.PromiseResult = value
    }

    reject(reason) {
        // 若是執行reject,狀態變爲rejected
        this.PromiseState = 'rejected'
        // 終值爲傳進來的reason
        this.PromiseResult = reason
    }
}
複製代碼

我們來測試一下代碼吧:函數

const test1 = new MyPromise((resolve, reject) => {
    resolve('成功')
})
console.log(test1) // MyPromise { PromiseState: 'fulfilled', PromiseResult: '成功' }

const test2 = new MyPromise((resolve, reject) => {
    reject('失敗')
})
console.log(test2) // MyPromise { PromiseState: 'rejected', PromiseResult: '失敗' }
複製代碼

2. 狀態不可變

其實上面的代碼是有問題的,什麼問題呢?看看:學習

const test1 = new MyPromise((resolve, reject) => {
    resolve('成功')
    reject('失敗')
})
console.log(test1) // MyPromise { PromiseState: 'rejected', PromiseResult: '失敗' }
複製代碼

正確的應該是狀態爲fulfilled,結果是成功,這裏明顯沒有以第一次爲準測試

以前說了,Promise只以第一次爲準,第一次成功就永久fulfilled,第一次失敗就永遠狀態爲rejected,具體是什麼流程呢?我給你們畫了一張圖:

Promise有三種狀態:

  • pending:等待中,是初始狀態
  • fulfilled:成功狀態
  • rejected:失敗狀態

一旦狀態從pending變爲fulfilled或者rejected,那麼此Promise實例的狀態就定死了。 截屏2021-08-01 下午12.33.10.png

其實實現起來也很容易,加個判斷條件就行:

resolve(value) {
        // state是不可變的
+        if (this.PromiseState !== 'pending') return
        // 若是執行resolve,狀態變爲fulfilled
        this.PromiseState = 'fulfilled'
        // 終值爲傳進來的值
        this.PromiseResult = value
    }

    reject(reason) {
        // state是不可變的
+        if (this.PromiseState !== 'pending') return
        // 若是執行reject,狀態變爲rejected
        this.PromiseState = 'rejected'
        // 終值爲傳進來的reason
        this.PromiseResult = reason
    }
複製代碼

再來看看效果:

const test1 = new MyPromise((resolve, reject) => {
    // 只以第一次爲準
    resolve('成功')
    reject('失敗')
})
console.log(test1) // MyPromise { PromiseState: 'fulfilled', PromiseResult: '成功' }
複製代碼

3. throw

截屏2021-08-01 下午12.57.17.png

Promise中有throw的話,就至關於執行了reject。這就要使用try catch

+        try {
            // 執行傳進來的函數
            executor(this.resolve, this.reject)
+        } catch (e) {
            // 捕捉到錯誤直接執行reject
+            this.reject(e)
+        }
複製代碼

我們來看看效果:

const test3 = new MyPromise((resolve, reject) => {
    throw('失敗')
})
console.log(test3) // MyPromise { PromiseState: 'rejected', PromiseResult: '失敗' }
複製代碼

then

我們平時使用then方法是這麼用的:

// 立刻輸出 」成功「
const p1 = new Promise((resolve, reject) => {
    resolve('成功')
}).then(res => console.log(res), err => console.log(err))

// 1秒後輸出 」失敗「
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('失敗')
    }, 1000)
}).then(res => console.log(res), err => console.log(err))

// 鏈式調用 輸出 200
const p3 = new Promise((resolve, reject) => {
    resolve(100)
}).then(res => 2 * res, err => console.log(err))
  .then(res => console.log(res), err => console.log(err))
複製代碼

能夠總結出這幾個知識點:

  • then接收兩個回調,一個是成功回調,一個是失敗回調
  • 當Promise狀態爲fulfilled執行成功回調,爲rejected執行失敗回調
  • 如resolve或reject在定時器裏,則定時器結束後再執行then
  • then支持鏈式調用,下一次then執行受上一次then返回值的影響

下面我們就一步一步地去實現他吧

1. 實現then

then(onFulfilled, onRejected) {
        // 接收兩個回調 onFulfilled, onRejected
        
        // 參數校驗,確保必定是函數
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

        if (this.PromiseState === 'fulfilled') {
            // 若是當前爲成功狀態,執行第一個回調
            onFulfilled(this.PromiseResult)
        } else if (this.PromiseState === 'rejected') {
            // 若是當前爲失敗狀態,執行第二哥回調
            onRejected(this.PromiseResult)
        }

    }
複製代碼

我們來看看效果:

// 輸出 」成功「
const test = new MyPromise((resolve, reject) => {
    resolve('成功')
}).then(res => console.log(res), err => console.log(err))
複製代碼

2. 定時器狀況

上面咱們已經實現了then的基本功能。那若是是定時器狀況呢?

仍是那個代碼,怎麼才能保證,1秒後才執行then裏的失敗回調呢?

// 1秒後輸出 」成功「
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('失敗')
    }, 1000)
}).then(res => console.log(res), err => console.log(err))
複製代碼

咱們不能確保1秒後才執行then函數,可是咱們能夠保證1秒後再執行then裏的回調,可能這裏你們有點懵逼,我一樣用一張圖給你們講講吧:

截屏2021-08-01 下午9.05.24.png

也就是在這1秒時間內,咱們能夠先把then裏的兩個回調保存起來,而後等到1秒事後,執行了resolve或者reject,我們再去判斷狀態,而且判斷要去執行剛剛保存的兩個回調中的哪個回調。

那麼問題來了,咱們怎麼知道當前1秒還沒走完甚至還沒開始走呢?其實很好判斷,只要狀態是pending,那就證實定時器還沒跑完,由於若是定時器跑完的話,那狀態確定就不是pending,而是fulfilled或者rejected

那是用什麼來保存這些回調呢?建議使用數組,由於一個promise實例可能會屢次then,用數組就一個一個保存了

initValue() {
        // 初始化值
        this.PromiseResult = null // 終值
        this.PromiseState = 'pending' // 狀態
+        this.onFulfilledCallbacks = [] // 保存成功回調
+        this.onRejectedCallbacks = [] // 保存失敗回調
    }

    resolve(value) {
        // state是不可變的
        if (this.PromiseState !== 'pending') return
        // 若是執行resolve,狀態變爲fulfilled
        this.PromiseState = 'fulfilled'
        // 終值爲傳進來的值
        this.PromiseResult = value
        // 執行保存的成功回調
+        while (this.onFulfilledCallbacks.length) {
+            this.onFulfilledCallbacks.shift()(this.PromiseResult)
+        }
    }

    reject(reason) {
        // state是不可變的
        if (this.PromiseState !== 'pending') return
        // 若是執行reject,狀態變爲rejected
        this.PromiseState = 'rejected'
        // 終值爲傳進來的reason
        this.PromiseResult = reason
        // 執行保存的失敗回調
+        while (this.onRejectedCallbacks.length) {
+            this.onRejectedCallbacks.shift()(this.PromiseResult)
+        }
    }
    
    then(onFulfilled, onRejected) {
        // 接收兩個回調 onFulfilled, onRejected

        // 參數校驗,確保必定是函數
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

        if (this.PromiseState === 'fulfilled') {
            // 若是當前爲成功狀態,執行第一個回調
            onFulfilled(this.PromiseResult)
        } else if (this.PromiseState === 'rejected') {
            // 若是當前爲失敗狀態,執行第二哥回調
            onRejected(this.PromiseResult)
+        } else if (this.PromiseState === 'pending') {
+            // 若是狀態爲待定狀態,暫時保存兩個回調
+            this.onFulfilledCallbacks.push(onFulfilled.bind(this))
+            this.onRejectedCallbacks.push(onRejected.bind(this))
+        }

    }

複製代碼

加完上面的代碼,我們來看看定時器的效果吧:

const test2 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('成功') // 1秒後輸出 成功
        // resolve('成功') // 1秒後輸出 失敗
    }, 1000)
}).then(res => console.log(res), err => console.log(err))
複製代碼

3. 鏈式調用

then支持鏈式調用,下一次then執行受上一次then返回值的影響,給你們舉個例子:

// 鏈式調用 輸出 200
const p3 = new Promise((resolve, reject) => {
    resolve(100)
}).then(res => 2 * res, err => console.log(err))
    .then(res => console.log(res), err => console.log(err))

// 鏈式調用 輸出300
const p4 = new Promise((resolve, reject) => {
    resolve(100)
}).then(res => new Promise((resolve, reject) => resolve(3 * res)), err => console.log(err))
    .then(res => console.log(res), err => console.log(err))
複製代碼

從上方例子,咱們能夠獲取到幾個知識點:

  • 一、then方法自己會返回一個新的Promise對象
  • 二、若是返回值是promise對象,返回值爲成功,新promise就是成功
  • 三、若是返回值是promise對象,返回值爲失敗,新promise就是失敗
  • 四、若是返回值非promise對象,新promise對象就是成功,值爲此返回值

我們知道then是Promise上的方法,那如何實現then完還能再then呢?很簡單,then執行後返回一個Promise對象就好了,就能保證then完還能繼續執行then:

截屏2021-08-01 下午9.06.02.png

代碼實現:

then(onFulfilled, onRejected) {
        // 接收兩個回調 onFulfilled, onRejected

        // 參數校驗,確保必定是函數
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }


        var thenPromise = new MyPromise((resolve, reject) => {

            const resolvePromise = cb => {
                try {
                    const x = cb(this.PromiseResult)
                    if (x === thenPromise) {
                        // 不能返回自身哦
                        throw new Error('不能返回自身。。。')
                    }
                    if (x instanceof MyPromise) {
                        // 若是返回值是Promise
                        // 若是返回值是promise對象,返回值爲成功,新promise就是成功
                        // 若是返回值是promise對象,返回值爲失敗,新promise就是失敗
                        // 誰知道返回的promise是失敗成功?只有then知道
                        x.then(resolve, reject)
                    } else {
                        // 非Promise就直接成功
                        resolve(x)
                    }
                } catch (err) {
                    // 處理報錯
                    reject(err)
                }
            }

            if (this.PromiseState === 'fulfilled') {
                // 若是當前爲成功狀態,執行第一個回調
                resolvePromise(onFulfilled)
            } else if (this.PromiseState === 'rejected') {
                // 若是當前爲失敗狀態,執行第二個回調
                resolvePromise(onRejected)
            } else if (this.PromiseState === 'pending') {
                // 若是狀態爲待定狀態,暫時保存兩個回調
                // 若是狀態爲待定狀態,暫時保存兩個回調
                this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled))
                this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected))
            }
        })

        // 返回這個包裝的Promise
        return thenPromise

    }
複製代碼

如今你們能夠試試效果怎麼樣了,你們要邊敲邊試哦:

const test3 = new MyPromise((resolve, reject) => {
    resolve(100) // 輸出 狀態:成功 值: 200
    // reject(100) // 輸出 狀態:失敗 值:300
}).then(res => 2 * res, err => 3 * err)
    .then(res => console.log(res), err => console.log(err))

const test4 = new MyPromise((resolve, reject) => {
    resolve(100) // 輸出 狀態:失敗 值:200
    // reject(100) // 輸出 狀態:成功 值:300
    // 這裏可沒搞反哦。真的搞懂了,就知道了爲啥這裏是反的
}).then(res => new MyPromise((resolve, reject) => reject(2 * res)), err => new MyPromise((resolve, reject) => resolve(2 * res)))
    .then(res => console.log(res), err => console.log(err))
複製代碼

4. 微任務

看過js執行機制的兄弟都知道,then方法是微任務,啥叫微任務呢?其實不知道也沒關係,我經過下面例子讓你知道:

const p = new Promise((resolve, reject) => {
    resolve(1)
}).then(res => console.log(res), err => console.log(err))

console.log(2)

輸出順序是 2 1
複製代碼

爲啥不是 1 2 呢?由於then是個微任務啊。。。一樣,咱們也要給咱們的MyPromise加上這個特性(我這裏使用定時器,你們別介意哈)

只須要讓resolvePromise函數異步執行就能夠了

const resolvePromise = cb => {
    setTimeout(() => {
        try {
            const x = cb(this.PromiseResult)
            if (x === thenPromise) {
                // 不能返回自身哦
                throw new Error('不能返回自身。。。')
            }
            if (x instanceof MyPromise) {
                // 若是返回值是Promise
                // 若是返回值是promise對象,返回值爲成功,新promise就是成功
                // 若是返回值是promise對象,返回值爲失敗,新promise就是失敗
                // 誰知道返回的promise是失敗成功?只有then知道
                x.then(resolve, reject)
            } else {
                // 非Promise就直接成功
                resolve(x)
            }
        } catch (err) {
            // 處理報錯
            reject(err)
        }
    })
}
複製代碼

看看效果:

const test4 = new MyPromise((resolve, reject) => {
    resolve(1)
}).then(res => console.log(res), err => console.log(err))

console.log(2)

輸出順序 2 1

複製代碼

其餘方法

這些方法都比較簡單,我就不太過詳細地講了,你們也能夠借這個機會,本身摸索,鞏固這篇文章的知識。

all

  • 接收一個Promise數組,數組中若有非Promise項,則此項當作成功
  • 若是全部Promise都成功,則返回成功結果數組
  • 若是有一個Promise失敗,則返回這個失敗結果
static all(promises) {
        const result = []
        let count = 0
        return new MyPromise((resolve, reject) => {
            const addData = (index, value) => {
                result[index] = value
                count++
                if (count === promises.length) resolve(result)
            }
            promises.forEach((promise, index) => {
                if (promise instanceof MyPromise) {
                    promise.then(res => {
                        addData(index, res)
                    }, err => reject(err))
                } else {
                    addData(index, promise)
                }
            })
        })
    }
複製代碼

race

  • 接收一個Promise數組,數組中若有非Promise項,則此項當作成功
  • 哪一個Promise最快獲得結果,就返回那個結果,不管成功失敗
static race(promises) {
        return new MyPromise((resolve, reject) => {
            promises.forEach(promise => {
                if (promise instanceof MyPromise) {
                    promise.then(res => {
                        resolve(res)
                    }, err => {
                        reject(err)
                    })
                } else {
                    resolve(promise)
                }
            })
        })
    }
複製代碼

allSettled

  • 接收一個Promise數組,數組中若有非Promise項,則此項當作成功
  • 把每個Promise的結果,集合成數組,返回
static allSettled(promises) {
        return new Promise((resolve, reject) => {
            const res = []
            let count = 0
            const addData = (status, value, i) => {
                res[i] = {
                    status,
                    value
                }
                count++
                if (count === promises.length) {
                    resolve(res)
                }
            }
            promises.forEach((promise, i) => {
                if (promise instanceof MyPromise) {
                    promise.then(res => {
                        addData('fulfilled', res, i)
                    }, err => {
                        addData('rejected', err, i)
                    })
                } else {
                    addData('fulfilled', promise, i)
                }
            })
        })
    }
複製代碼

any

any與all相反

  • 接收一個Promise數組,數組中若有非Promise項,則此項當作成功
  • 若是有一個Promise成功,則返回這個成功結果
  • 若是全部Promise都失敗,則報錯
static any(promises) {
        return new Promise((resolve, reject) => {
            let count = 0
            promises.forEach((promise) => {
                promise.then(val => {
                    resolve(val)
                }, err => {
                    count++
                    if (count === promises.length) {
                        reject(new AggregateError('All promises were rejected'))
                    }
                })
            })
        })
    }
}
複製代碼

結語

不再怕面試官問你Promise原理啦哈哈哈哈😁

若是你以爲此文章對你有一丁點幫助的話,點個讚唄,謝謝你

學習羣請點這裏

image.png

相關文章
相關標籤/搜索