自己实现Promise

357 阅读6分钟

Promise

为什么要自己实现一个promise

深入研究Promise设计思想、Promise A+规范

开始

  • 查看promise,可从vscode中查看
declare type PromiseConstructorLike = new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void) => PromiseLike<T>;

promise的构造函数接收一个执行器函数executor其中接收两个参数resolve,reject, resolve可将promise状态更改为fulfilled, reject可将promise状态更改为rejected

// 根据上述定义可写出初步代码
function Promise(excutor){
    this.state = 'pending'
    const self = this
    function resolve(value) {
        self.state = 'fulfilled'
    }
    function reject(reason) {
        self.state = 'rejected'
    }
    excutor(resolve, reject)
}

以下是翻译以及根据规范的实现思路

一. 术语

  1. “promise” 是一个拥有then方法并符合规范的对象或者函数。
  2. “thenable” 是定义then方法的对象或函数
  3. “value” 是任何合法的JavaScript值(包括undefined,thenable 或 promise)
  4. “exception” 是throw抛出的异常值
  5. “reason” 是表示rejected状态的值

二. 规范要求

Promise States (状态)

promise 必须存在于 pending、 fulfilled、 rejected三种状态之一

  1. 当处于pending状态时,可转变为fulfilled或rejected状态
  2. 当处于fulfilled状态时不可转变状态,并具有一个不可更改的value
  3. 当处于rejected状态时不可转变状态,并具有一个不可更改的reason
//根据上述规范完善代码
function Promise(executor) {
    this.state = 'pending' // 初始pending状态
    this.value = null // 保存fulfilled状态的value
    this.reason = null // 保存rejected状态的reason
    const self = this
    self.onResolvedCallbacks = [] // 需要两个队列来存储pending状态的回调下文会有提及
	self.onRejectedCallbacks = []
    function resolve(value) {
        if (self.state === 'pending') {
            self.state = 'fulfilled'
            self.value = value
            self.onResolvedCallbacks.forEach(fn =>{
                fn(self.value)}
            )
        }
    }
    function reject(reason) {
        if(self.state === 'pending') {
            self.state = 'rejected'
            self.reason = reason
            self.onRejectedCallbacks.forEach(fn => {
                fn(self.reason)
            })
        }
    }
    executor(resolve, reject)
}

then方法

promise必须提供一个then方法来访问其value或reason,then方法接收两个参数(onFulfilled, onRejected)

  1. onFulfilled, onRejected是两个可选参数,如果他们不是函数,则会被忽略
  2. 如果onFulfilled是一个函数,必须在promise状态为fulfilled时被调用,并且将value传入onFulfilled第一个参数,不能调用多次
  3. 如果onRejected是一个函数,必须在promise状态为rejected时被调用,并且将reason传入onRejected第一个参数,不能多次调用
  4. onFulfilled onRejected在执行上下文包含其他代码时(可理解为其需要一个纯净的堆栈)平台代码除外之前不可被调用(在note3.1中说明可使用宏任务、微任务队列如setTimeout,setImmediate,mutationobserver,process.nexttick)
  5. onFulfilled onRejected只能被当成函数执行,this指向全局对象或undefined,取决于是否为严格模式。
  6. then方法可能会在同个promise被调用多次,当(如果)promise完成或拒绝时onFulfilled,onRejected回调必须按照其调用顺序执行then(此时需要两个队列,用来保存回调)
Promise.prototype.then = function(onFulfilled, onRejected) {
    const self = this
    if(self.state === 'fulfilled') { // "2"
        setTimeout(() => { // "4"
            typeof onFulfilled === 'function' && onFulfilled(self.value) // "1,5"
        },0)
    }
    if(self.state === 'rejected') { // "3"
        setTimeout(() => { // "4"
            typeof onRejected === 'function' && onRejected(self.reason) // "1,5"
        },0)
    }
    // pending状态处理 发布订阅
    if(self.state === 'pending') {
        self.onResolvedCallbacks.push(() => { // "6"
          typeof onFulfilled === 'function' && onFulfilled(self.value)
        })
        self.onRejectedCallbacks.push(() => { // "6"
            typeof onRejected === 'function' && onRejected(self.reason)
        })
    }
}
  1. then方法返回值必须是一个新的promisepromise2 = promise1.then() // 下文会引用promise1 promise2
  2. 如果onFulfilled onRejected返回一个value x 需要执行Promise Resolution Procedure(下文会详细说明)
  3. 如果onFulfilled onRejected抛出一个异常e,promise2要捕获这个异常e并将其作为reason置为rejected状态
  4. 如果onFulfilled不是一个函数并且promise1状态是fulfilled,则promise2状态必须置为fufilled,并且有与promise1相同的value
  5. 如果onRejected不是一个函数并且promise1状态是rejected,则promise2状态必须置为rejected,并且有与promise1相同的reason
Promise.prototype.then = function(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val // "10"
    onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e } // "11"
    const self = this
    const promise2 = new Promise((resolve, reject) => { // "7"
        if(self.state === 'fulfilled') {
            try { // '9'
                setTimeout(() => {
                    const x = onFulfilled(self.value) // '8'
                    resolvePromise(promise2, x, resolve, reject) // '8'
                },0)
            } catch (e) {
                reject(e)
            }
        }
        if(self.state === 'rejected') {
            try { // '9'
                setTimeout(() => {
                    const x = onRejected(self.reason) // '8'
                    resolvePromise(promise2, x, resolve, reject) // '8'
                },0)
            } catch (e) {
                reject(e)
            }
        }
        // pending状态处理 发布订阅
        if(self.state === 'pending') {
            self.onResolvedCallbacks.push(() => {
                onFulfilled(self.value)
            })
            self.onRejectedCallbacks.push(() => {
                onRejected(self.reason)
            })
        }
    })
    return promise2 // "7"
}

Promise Resolution Procedure

需要让不同的promise代码互相套用 , 统一叫做resolvePromise

  1. 如果promise2与x指向同一对象,置为reject并抛出TypeError(自己不能等待自己完成)
  2. 如果x是一个promise,将采用x的状态为promise2的状态,promise2的value与reason与x相同
  3. 其他情况,如果x是一个函数或对象,使then = x.then
  4. 如果检索属性x.then异常需要捕获其错误作为promise2的reason
  5. 如果then是函数则将x作为其this指向,第一个参数为resolvePromise,第二个参数为rejectPromise
  6. resolvePromise接收一个参数y 并且运行Promise Resolution Procedure resolvePromise
  7. rejectPromise接收一个参数r,并且将promise2状态置为reject
  8. resolvePromise与rejectPromise两者只能调用其一,并且只能调用一次。
  9. 如果调用then发生异常,抛弃resolvePromise,rejectPromise执行结果,捕获异常作为promise2的reason
  10. 如果then不是一个函数将promise2置为resolve并将x作为promise2的value
  11. 如果x不是一个函数或对象将promise2置为resolve并将x作为promise2的value
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) { // '1'
        return reject(new TypeError('Circular reference error'))
    }
    let called = false // '8'
    if(x != null && (typeof x === 'object' || typeof x === 'function')) { // '11'
        try { // '4'
            let then = x.then // '3'
            if(typeof then === 'function') {
                try {
                    then.call(x, (y) => { // '6'
                        if(called) return
                        called = true
                        resolvePromise(x, y, resolve, reject)
                    }, (r) => { // '7'
                        if(called) return
                        called = true
                        reject(r)
                    })
                } catch (e) { // '9' 可以与外层try catch共用一个可删去
                    if(called) return
                    called = true
                    reject(e)   
                }
            } else { // '10'
                resolve(x)
            }
        } catch(e) {
            if(called) return
            called = true
            reject(e)
        }
    } else {
        resolve(x)
    }
}
  • excutor可能会执行出错,需要捕获错误
function Promise(excutor) {
    ....
    try {
        excutor(resolve, reject)    
    } catch (e) {
        reject(e)   
    }
}

完整代码

function Promise(excutor) {
    this.state = 'pending' // 初始pending状态
    this.value = null // 保存fulfilled状态的value
    this.reason = null // 保存rejected状态的reason
    const self = this
    self.onResolvedCallbacks = [] // 需要两个队列来存储pending状态的回调下文会有提及
	self.onRejectedCallbacks = []
    function resolve(value) {
        if (self.state === 'pending') {
            self.state = 'fulfilled'
            self.value = value
            self.onResolvedCallbacks.forEach(fn =>{
                fn(self.value)}
            )
        }
    }
    function reject(reason) {
        if(self.state === 'pending') {
            self.state = 'rejected'
            self.reason = reason
            self.onRejectedCallbacks.forEach(fn => {
                fn(self.reason)
            })
        }
    }
    excutor(resolve, reject)
}
Promise.prototype.then = function(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val // "10"
    onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e } // "11"
    const self = this
    const promise2 = new Promise((resolve, reject) => {
        if(self.state === 'fulfilled') {
            try { // '9'
                setTimeout(() => {
                    const x = onFulfilled(self.value) // '8'
                    resolvePromise(promise2, x, resolve, reject) // '8'
                },0)
            } catch (e) {
                reject(e)
            }
        }
        if(self.state === 'rejected') {
            try { // '9'
                setTimeout(() => {
                    const x = onRejected(self.reason) // '8'
                    resolvePromise(promise2, x, resolve, reject) // '8'
                },0)
            } catch (e) {
                reject(e)
            }
        }
        // pending状态处理 发布订阅
        if(self.state === 'pending') {
            self.onResolvedCallbacks.push(() => {
                onFulfilled(self.value)
            })
            self.onRejectedCallbacks.push(() => {
                onRejected(self.reason)
            })
        }
    })
    return promise2
}

function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) { // '1'
        return reject(new TypeError('Circular reference error'))
    }
    let called = false // '8'
    if(x != null && (typeof x === 'object' || typeof x === 'function')) { // '11'
        try { // '4'
            let then = x.then // '3'
            if(typeof then === 'function') {
                then.call(x, (y) => { // '6'
                    if(called) return
                    called = true
                    resolvePromise(x, y, resolve, reject)
                }, (r) => { // '7'
                    if(called) return
                    called = true
                    reject(r)
                })
            } else { // '10'
                resolve(x)
            }
        } catch(e) {
            if(called) return
            called = true
            reject(e)
        }
    } else {
        resolve(x)
    }
}

三、测试用例

  • promises-aplus-tests插件可供我们进行promise测试
// 全局安装
npm install promises-aplus-tests -g
  • 需要在我们的文件里添加一段代码,供测试使用
// 实现一个promise的延迟对象 defer()
Promise.deferred = function() {
	let dfd = {}
	dfd.promise = new Promise((resolve, reject) => {
		dfd.resolve = resolve
		dfd.reject = reject
	})
	return dfd
}
module.exports = Promise
  • 命令行输入promises-aplus-tests 被测试文件名称
// 例如文件名为 promise.js
promises-aplus-tests promise.js

四、promise其他方法实现

Promise.resolve Promise.reject

Promise.prototype.resolve = function (value) {
    return new Promise((resolve, reject) => {
        resolve(value)
    })
}
Promise.prototype.reject = function (reason) {
    return new Promise((resolve, reject) => {
        reject(reason)
    })
}

Promise.all

  • Promise.all接收一个可迭代对象Map Set Array
Promise.prototype.all = function (promiseArray) {
    let valueList = []
    let num = 0
    return new Promise((resolve, reject) => {
        try {
            let index = 0
            for(let item of promiseArray) {
                if(typeof item === 'object' && typeof item !== null && typeof item.then === 'function') {
                    ;(function (index) {
                        item.then(value => {
                            valueList[index] = value
                            num++
                            if(num === promiseArray.length) return resolve(valueList)
                        }).catch(e => { reject(e) })
                    })(index)
                } else {
                    valueList[index] = item
                    num++
                    if(num === promiseArray.length) return resolve(valueList)
                }
                index++
            }
        } catch(e) {
            throw new Error(e)
            reject(e)
        }
    })
}

Promise.retry

Promise.retry = function(promise, times, delay) {
    return new Promise((resolve, reject) => {
        function attemp() {
            promise().then((data) => {
                resolve(data)
            }).catch((err) => {
                if (times === 0) {
                    reject(err)
                } else {
                    times--
                    setTimeout(attemp, delay)
                }
            })
        }
        attemp()
    })
}

Promise.race

Promise.prototype.race = function(promises) {
    return new Promise((resolve,reject) =>{
        for(let i = 0 ; i < promises.length ; i++) {
            promises[i].then(resolve,reject);
        }
    })
}