史上最完整promise源碼手寫實現


史上最完整的promise源碼實現,哈哈,之所以用這個標題,是因為開始用的標題《手寫promise源碼》不被收錄

promise自我介紹

promise : "君子一諾千金,承諾的事情一定會去執行"

promise的使用場景

  • 使用promise能夠有效的解決js異步回調地獄問題
  • 能夠將業務邏輯與數據處理分隔開使代碼更優雅,方便閱讀,更有利於代碼維護

promise的基本用法

function promiseTest() {
    let promise = new Promise((resolve, reject) => {
        let r = parseInt(Math.random() * 10)
        if (r % 2 == 0) {
            resolve('成功')
        } else {
            reject('失敗')
        }
    })
    return promise
}
const promise = promiseTest()
promise.then((data) => {
    console.log(data)
}).catch((err) => {
    console.log(err)
})

先來分析一下promise的規范

  • promise有三種狀態:pending,fulfilled,rejected。pending代表等待的狀態,在此狀態下,可能執行resolve()的方法,也可能執行reject()方法,fulfilld代表成功態,此狀態下執行resolve()方法,rejected代表失敗態,此狀態下執行reject()方法,一旦成功了就不能失敗,反過來也是一樣
  • 每個promsie都有一個then方法
  • 如果new promise 報錯了會走失敗態(throw new Error('報錯')也會走失敗態)
// 手寫promise源碼
// 第一步:基礎代碼
class Mypromise {
    constructor(executor) {
        this.state = 'pending'  //狀態值
        this.value = undefined	//成功的返回值
        this.reason = undefined //失敗的返回值
        // 成功
        let resolve = (value) => {
            if (this.state == 'pending') {
                this.state = 'fullFilled'
                this.value = value
            }
        }
        // 失敗
        let reject = (reason) => {
            if (this.state == 'pending') {
                this.state = 'rejected'
                this.reason = reason
            }
        }
        try {
            // 執行函數
            executor(resolve, reject)
        } catch (err) {
            // 失敗則直接執行reject函數
            reject(err)
        }
    }
    then(onFullFilled, onRejected) {
        // 狀態為fulfuilled,執行onFullFilled,傳入成功的值
        if (this.state == 'fullFilled') {
            onFullFilled(this.value)
        }
        // 狀態為rejected,執行onRejected,傳入失敗的值
        if (this.state == 'rejected') {
            onRejected(this.reason)
        }
    }
}

const p = new Mypromise((resolve, reject) => {
    // resolve('success')   // 走了成功就不會走失敗了
    throw new Error('失敗') // 失敗了就走resolve
    reject('failed')       // 走了失敗就不會走成功
})
p.then((res) => {
    console.log(res)
}, (err) => {
    console.log(err)
})

此時九陽神功的第一層就算完成了

但是當碰到異步調用的時候,上面的代碼就會卡在pending態,神功初成,還要繼續往下修煉,若不能繼續突破,則無法上升到第二層境界

如下調用的時候會卡住,無法執行

const p = new Mypromise((resolve, reject) => {
    setTimeout(function() {
        resolve('success')
    }, 1000)
})
p.then((res) => {
    console.log(res)
}, (err) => {
    console.log(err)
})

此時我們使用一個發布訂閱者模式,在pending狀態的時候將成功的函數和失敗的函數存到各自的回調隊列數組中,等一旦reject或者resolve,就調用它們:

在pending態的時候將所有的要在成功態執行的方法都存到onResolveCallbacks數組中

當狀態變化的時候,就執行發布他們

下面是完整的代碼

class Mypromise {
    constructor(executor) {
        this.status = 'pending'  //狀態值
        this.value = undefined   //成功的返回值
        this.reason = undefined	 //失敗的返回值
        this.onResolvedCallbacks = [] //成功的回調函數
        this.onRejectedCallbacks = [] //失敗的回調函數
        // 成功
        let resolve = (value) => {
            // pending用來屏蔽的,resolve和reject只能調用一個,不能同時調用,這就是pending的作用
            if (this.status == 'pending') {
                this.status = 'fullFilled'
                this.value = value
                // 發布執行函數
                this.onResolvedCallbacks.forEach(fn => fn())
            }
        }
        // 失敗
        let reject = (reason) => {
            if (this.status == 'pending') {
                this.status = 'rejected'
                this.reason = reason
                //失敗執行函數
                this.onRejectedCallbacks.forEach(fn => fn())
            }
        }
        try {
            // 執行函數
            executor(resolve, reject)
        } catch (err) {
            // 失敗則直接執行reject函數
            reject(err)
        }
    }
    then(onFullFilled, onRejected) {
        // 同步
        if (this.status == 'fullFilled') {
            onFullFilled(this.value)
        }
        if (this.status == 'rejected') {
            onRejected(this.reason)
        }
        // 異步
        if (this.status == 'pending') {
            // 在pending狀態的時候先訂閱
            this.onResolvedCallbacks.push(() => {
                // todo
                onFullFilled(this.value)
            })
            this.onRejectedCallbacks.push(() => {
                // todo
                onRejected(this.reason)
            })
        }
    }
}

const p = new Mypromise((resolve, reject) => {
    setTimeout(function() {
        // resolve('success') // 異步調用的時候,this.status一直是pending狀態,不會執行代碼了,因此要改裝成發布訂閱者模式
        reject('failed')
    }, 1000)
    // resolve('success') // 走了成功就不會走失敗了
    // throw new Error('失敗') // 失敗了也會走resolve
    // reject('failed')
})
p.then((res) => {
    console.log(res)
}, (err) => {
    console.log(err)
})
p.then((res) => {
    console.log(res)
}, (err) => {
    console.log(err)
})
p.then((res) => {
    console.log(res)
}, (err) => {
    console.log(err)
})

恭喜你少年,九陽神功第二層你已經學會了

接下來接續學習神功第三層promise的鏈式調用

要達到鏈式調用我們就要采用“老和尚給小和尚講故事”的遞歸大法了,也可以說是愚公移山大法,很多同學可能一直對遞歸函數有點懼怕,其實很簡單,想一下小時候聽過的老和尚講故事,以及愚公移山的故事就清楚什么是遞歸算法了。

我們先來回味一下這兩個經典的故事:

“老和尚給小和尚講故事:從前有座山,山里有座廟,廟里有個老和尚,老和尚給小和尚講故事,從前有座山,山里有座廟......”

愚公說:“雖我之死,有子存焉;子又生孫,孫又生子;子又有子,子又有孫;子子孫孫無窮匱也,而山不加增,何苦而不平”

遞歸是不是很簡單呢?

不了解規范的同學先去看一下Promises/A+規范文檔:

英文規范文檔:https://promisesaplus.com/

下面先來分析一下鏈式調用的用法,以及then里面可能出現的情況

const p = new Promise((resolve, reject) => {
    resolve(100)
})
p.then((data) => {
    return 100 * data
}, (err) => {
    console.log(err)
}).then((data) => {
    return new Promise((resolve, reject) => {
        console.log(data)
        resolve(data)
    })
}).then((data) => {
    console.log('result', data) // 10000
})

根據原生promise的then的用法,我們總結一下:

1.then方法如果返回一個普通的值,我們就將這個普通值傳遞給下一個then

2.then方法如果返回一個promise對象,我們就將這個promise對象執行結果返回到下一個then

普通的值傳遞很好辦,我們將第一次then的onFulfilled函數返回的值存到x變量里面,在然后resolve出去就可以了

then(onFullFilled, onRejected) {
        // 這樣就是一個遞歸
        let promise2 = new Mypromise((resolve, reject) => {
            // 函數里面調函數就跟第一次使用一樣,主要的是這里面的this指向怎么變化的
            // 同步
            let x
            console.log('this', this)
            if (this.status == 'fullFilled') {
                // 箭頭函數,無論this一直是指向最外層的對象
                x = onFullFilled(this.value)
                resolve(x) // resolve(x) // 這一步x只能處理普通值,但是x可能是一個函數對象,或者promise,所以要對x進行判斷
                // 添加一個resolvePromise()的方法來判斷x跟promise2的狀態,決定promise2是走成功還是失敗
            }
            if (this.status == 'rejected') {
                x = onRejected(this.reason)
                reject(x)
            }
            // 異步
            if (this.status == 'pending') {
                // 在pending狀態的時候先訂閱
                this.onResolvedCallbacks.push(() => {
                    // todo
                    x = onFullFilled(this.value)
                    resolve(x)
                })
                this.onRejectedCallbacks.push(() => {
                    // todo
                    x = onRejected(this.reason)
                    resolve(x)
                })
            }
        })
        return promise2   //then方法返回一個promise對象
    }

復雜的是then里面返回的是一個promise的時候怎么辦,因為返回的promise的我們要判斷他執行的狀態,來決定是走成功態,還是失敗態,這時候我們就要寫一個判斷的函數resolvePromise(promise2, x, resolve, reject)來完成這個判斷

then(onFullFilled, onRejected) {
        // 這樣就是一個遞歸
        let promise2 = new Mypromise((resolve, reject) => {
            // 箭頭函數,無論this一直是指向最外層的對象
            // 同步
            let x
            if (this.status == 'fullFilled') {
                setTimeout(() => {
                    try {
                        x = onFullFilled(this.value)
                        // 添加一個resolvePromise()的方法來判斷x跟promise2的狀態,決定promise2是走成功還是失敗
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (err) { // 中間任何一個環節報錯都要走reject()
                        reject(err)
                    }
                }, 0) // 同步無法使用promise2,所以借用setiTimeout異步的方式
                // MDN 0>=4ms
            }
            if (this.status == 'rejected') {
                setTimeout(() => {
                    try {
                        x = onRejected(this.value)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (err) { // 中間任何一個環節報錯都要走reject()
                        reject(err)
                    }
                }, 0) // 同步無法使用promise2,所以借用setiTimeout異步的方式
            }
            // 異步
            if (this.status == 'pending') {
                // 在pending狀態的時候先訂閱
                this.onResolvedCallbacks.push(() => {
                    // todo
                    setTimeout(() => {
                        try {
                            x = onFullFilled(this.value)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (err) { // 中間任何一個環節報錯都要走reject()
                            reject(err)
                        }
                    }, 0) // 同步無法使用promise2,所以借用setiTimeout異步的方式
                })
                this.onRejectedCallbacks.push(() => {
                    // todo
                    setTimeout(() => {
                        try {
                            x = onRejected(this.value)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (err) { // 中間任何一個環節報錯都要走reject()
                            reject(err)
                        }
                    }, 0) // 同步無法使用promise2,所以借用setiTimeout異步的方式
                })
            }
        })
        return promise2
    }

下面再來實現核心的resolvePromise方法

這個方法的主要作用是用來判斷x的值,如果x的值是一個普通的值,就直接返回x的值,如果x的值是一個promise,就要返回x.then() 執行的結果,核心代碼如下

const resolvePromise = (promise2, x, resolve, reject) => {
    // x和promise2不能是同一個人,如果是同一個人就報錯
    if (promise2 === x) {
        return reject(
            new TypeError('Chaining cycle detected for promise #<promise>')
        )
    }
    // 判斷如果x是否是一個對象,判斷函數是否是對象的方法有:typeof instanceof constructor toString
    if (typeof x === 'object' && x != null || typeof x === 'function') {
        try {
            let then = x.then // 取then可以報錯,報錯就走reject()
            if (typeof then === 'function') {
                // 用then.call()為了避免在使用一次x.then報錯
                then.call(x, y => {
                    console.log('y', y)
                    resolve(y)// 采用promise的成功結果,並且向下傳遞
                }, r => {
                    reject(r)// 采用promise的失敗結果,並且向下傳遞
                })
            } else {
                resolve(x)// x不是一個函數,是一個對象
            }
        } catch (err) {
            reject(err)
        }
    } else {
        // x是一個普通值
        resolve(x)
    }
}

細節的地方看注釋

此時基本的情況都已經實現的差不多了,下面還一種如下的情況,x的值里面包含有promise

const p2 = p.then((data) => {
    return new Mypromise((resolve, reject) => {
        resolve(new Mypromise((resolve, reject) => {
            setTimeout(() => {
                resolve(data * 1000)
            }, 1000)
        }))// 這里很可能又是一個promise函數
    })
})

我們只需要在判斷x的值的時候多調用一個回調,就可以解決以上的問題

1573458987688

下面是完整的源碼:

const isFunction = (value) => typeof value === 'function'
const PENDING = 'pending'
const RESOLVED = 'fulFilled'
const REJECTED = 'rejected'
const resolvePromise = (promise2, x, resolve, reject) => {
    // x和promise2不能是同一個人,如果是同一個人就報錯
    // 加一個開關,防止多次調用失敗和成功,跟pending狀態值一樣的邏輯一樣,走了失敗就不能走成功了,走了成功一定不能在走失敗
    if (promise2 === x) {
        return reject(
            new TypeError('Chaining cycle detected for promise #<promise>')
        )
    }
    // 判斷如果x是否是一個對象,判斷函數是否是對象的方法有:typeof instanceof constructor toString
    if ((typeof x === 'object' && x != null) || typeof x === 'function') {
        let called
        try { // 預防取.then的時候錯誤
            let then = x.then // Object.definePropertype
            if (typeof then === 'function') {
                // 用then.call()為了避免在使用一次x.then報錯
                then.call(x, y => {
                    // resolve(y)// 采用promise的成功結果,並且向下傳遞
                    if (called) {
                        return
                    }
                    called = true
                    // y有可能是一個promise,那么我們就要繼續使用回調函數,直到解析出來的值是一個普通值
                    resolvePromise(promise2, y, resolve, reject)
                }, r => {
                    if (called) {
                        return
                    }
                    called = true
                    reject(r)// 采用promise的失敗結果,並且向下傳遞
                })
            } else {
                if (called) {
                    return
                }
                called = true
                resolve(x)// x不是一個函數,是一個對象
            }
        } catch (err) {
            if (called) {
                return
            }
            called = true
            reject(err)
        }
    } else {
        // x是一個普通值
        resolve(x)
    }
}
class MyPromise {
    constructor(executor) {
        this.status = PENDING
        this.value = undefined
        this.reason = undefined
        this.onResolvedCallbacks = []
        this.onRejectedCallbacks = []
        // 成功
        let resolve = (value) => {
            // pending最屏蔽的,resolve和reject只能調用一個,不能同時調用,這就是pending的作用
            if (this.status == PENDING) {
                this.status = RESOLVED
                this.value = value
                // 發布執行函數
                this.onResolvedCallbacks.forEach(fn => fn())
            }
        }
        // 失敗
        let reject = (reason) => {
            if (this.status == PENDING) {
                this.status = REJECTED
                this.reason = reason
                this.onRejectedCallbacks.forEach(fn => fn())
            }
        }
        try {
            // 執行函數
            executor(resolve, reject)
        } catch (err) {
            // 失敗則直接執行reject函數
            reject(err)
        }
    }
    then(onFulFilled, onRejected) {
        // onfulfilled, onrejected 都是可選參數
        onFulFilled = isFunction(onFulFilled) ? onFulFilled : data => data
        onRejected = isFunction(onRejected) ? onRejected : err => {
            throw err
        }
        let promise2 = new MyPromise((resolve, reject) => {
            // 箭頭函數,無論this一直是指向最外層的對象
            // 同步
            if (this.status == RESOLVED) {
                setTimeout(() => {
                    try {
                        let x = onFulFilled(this.value)
                        // 添加一個resolvePromise()的方法來判斷x跟promise2的狀態,決定promise2是走成功還是失敗
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (err) { // 中間任何一個環節報錯都要走reject()
                        reject(err)
                    }
                }, 0) // 同步無法使用promise2,所以借用setiTimeout異步的方式
                // MDN 0>=4ms
            }
            if (this.status == REJECTED) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (err) { // 中間任何一個環節報錯都要走reject()
                        reject(err)
                    }
                }, 0) // 同步無法使用promise2,所以借用setiTimeout異步的方式
            }
            // 異步
            if (this.status == PENDING) {
                // 在pending狀態的時候先訂閱
                this.onResolvedCallbacks.push(() => {
                    // todo
                    setTimeout(() => {
                        try {
                            let x = onFulFilled(this.value)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (err) { // 中間任何一個環節報錯都要走reject()
                            reject(err)
                        }
                    }, 0) // 同步無法使用promise2,所以借用setiTimeout異步的方式
                })
                this.onRejectedCallbacks.push(() => {
                    // todo
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (err) { // 中間任何一個環節報錯都要走reject()
                            reject(err)
                        }
                    }, 0) // 同步無法使用promise2,所以借用setiTimeout異步的方式
                })
            }
        })
        return promise2
    }
}

到此核心的代碼就寫完了,我們用promises-aplus-tests插件來檢測一下

安裝:npm install promises-aplua-tests -g插件

加上如下代碼:

MyPromise.defer = MyPromise.deferred = function() {
    let dfd = {}
    dfd.promise = new MyPromise((resolve, reject) => {
        dfd.resolve = resolve
        dfd.reject = reject
    })
    return dfd
}
module.exports = MyPromise

執行命令:promises-aplus-tests promise.js

ok,非常完美

到此核心的代碼都已經實現了,后面的靜態方法,catch,all,race,resolve,reject,finally方法就是小試牛刀了,就不在贅述,只貼上代碼

catch方法:

// catch方法
MyPromise.prototype.catch = function(onReJected) {
    // 返回一個沒有第一個參數的then方法
    return this.then(undefined, onReJected)
}

promise的all方法

// 寫一個判斷函數是否是一個promise的方法
const isPromise = (value) => {
    if ((value != null && typeof value === 'object') || typeof value === 'function') {
        if (typeof value.then == 'function') {
            return true
        }
    } else {
        return false
    }
}
// static all方法
MyPromise.all = (lists) => {
    // 返回一個promise
    return new MyPromise((resolve, reject) => {
        let resArr = [] // 存儲處理的結果的數組
        // 判斷每一項是否處理完了
        let index = 0
        function processData(i, data) {
            resArr[i] = data
            index += 1
            if (index == lists.length) {
                // 處理異步,要使用計數器,不能使用resArr==lists.length
                resolve(resArr)
            }
        }
        for (let i = 0; i < lists.length; i++) {
            if (isPromise(lists[i])) {
                lists[i].then((data) => {
                    processData(i, data)
                }, (err) => {
                    reject(err) // 只要有一個傳入的promise沒執行成功就走reject
                    return
                })
            } else {
                processData(i, lists[i])
            }
        }
    })
}

promise的race方法

// promise的race方法
// 兩個方法賽跑,哪個贏了就先返回哪個的狀態
MyPromise.race = (lists) => {
    return new MyPromise((resolve, reject) => {
        for (let i = 0; i < lists.length; i++) {
            if (isPromise(lists[i])) {
                lists[i].then((data) => {
                    resolve(data)// 哪個先完成就返回哪一個的結果
                    return
                }, (err) => {
                    reject(err)
                    return
                })
            } else {
                resolve(lists[i])
            }
        }
    })
}

promise的靜態方法resolve()

// 靜態resolve方法
MyPromise.resolve = (value) => {
    // 如果是一個promise對象就直接將這個對象返回
    if (isPromise(value)) {
        return value
    } else {
        // 如果是一個普通值就將這個值包裝成一個promise對象之后返回
        return new MyPromise((resolve, reject) => {
            resolve(value)
        })
    }
}

promise的reject()方法

// 靜態reject方法
MyPromise.reject = (value) => {
    return new MyPromise((resolve, reject) => {
        reject(value)
    })
}

promise的finally方法

// 終極方法finally finally其實就是一個promise的then方法的別名,在執行then方法之前,先處理callback函數
MyPromise.prototype.finally = function(cb) {
    return this.then(
        value => MyPromise.resolve(cb()).then(() => value)
        ,
        reason => MyPromise.reject(cb()).then(() => { throw reason })
    )
}

少年,恭喜你promise神功已經修煉大成,是不是以為自己很牛逼,可以下山去行走江湖,劫富濟貧,鏟奸除惡了,莫急,修煉成promise大法,還只是剛剛開始,前面還有很多大法等着你呢,還有很多怪等你去打完,升級,學不會,就別先想着下山去吧,無論什么時候都要記住,山外有山,天外有天,技術精進一刻也不能怠慢。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM