写给自己看:记一次手写Promise

496 阅读6分钟

1. 术语

  • Promise 是类
  • promise 是一个拥有 then 方法的对象或函数,其行为符合本规范。
  • thenable 是一个定义了 then 方法的对象或函数。
  • value 指任何 JavaScript 的合法值(包括 undefined , thenable 和 promise)。
  • exception 是使用 throw 语句抛出的一个值。
  • reason 表示一个 promise 的拒绝原因。

2. 要求

2.1 Promise 状态

一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。

如果是pending状态,则promise可以转换到fulfilled或rejected状态。

如果是fulfilled状态,则promise不能转换成任何其它状态,必须有一个值(value),且这个值不能被改变。

如果是rejected状态,则promise不能转换成任何其它状态,必须有一个原因(reason),且这个值不能被改变。

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class Promise {

    state = PENDING
    value = undefined
    reason = undefined
    
    resolve (value) {
        if (this.state !== PENDING) return
        this.state = FULFILLED
        this.value = value
    }

    reject (reason) {
        if (this.state !== PENDING) return
        this.state = REJECTED
        this.reason = reason
    }
}

2.2 then 方法

一个 promise 必须提供一个 then 方法以访问其当前值、终值和据因。

promise 的 then 方法接受两个参数:

promise.then(onFulfilled, onRejected)

2.2.1 onFulfilled 和 onRejected 都是可选参数

如果 onFulfilled 不是函数,其必须被忽略。

如果 onRejected 不是函数,其必须被忽略。

class Promise {

    // ...
    
    then (onFulfilled, onRejected) {
    
        if (typeof onFulfilled === 'function') {
            // to do....
        }
    
        if (typeof onRejected === 'function') {
            // to do....
        }
    }
}

2.2.2 如果 onFulfilled 是函数

它必须在 promise 完成(fulfilled)后被调用,且 promise 的 value 作为其第一个参数。

它在 promise 完成(fulfilled)之前绝对不能被调用。

它不能被多次调用。


class Promise {

    // ...

    onFulfilledFn = undefined

    resolve (value) {
        if (this.state !== PENDING) return
        this.state = FULFILLED
        this.value = value
        if (onFulfilledFn) onFulfilledFn(value)
    }
    
    then (onFulfilled) {
        if (typeof onFulfilled === 'function') {
            onFulfilledFn = onFulfilled
        }
    }
}

2.2.3 如果 onRejected 是函数

它必须在 promise 被拒绝(rejected)后被调用,且 promise 的 reason 作为其第一个参数。

它在 promise 被拒绝(rejected)之前绝对不能被调用。

它不能被多次调用。


class Promise {

    // ...

    onRejectedFn = undefined
    
    reject (reason) {
        if (this.state !== PENDING) return
        this.state = REJECTED
        this.reason = reason
        if (onRejectedFn) onRejectedFn(reason)
    }
    
    then (onFulfilled, onRejected) {
        if (typeof onRejected === 'function') {
            onRejectedFn = onRejected
        }
    }
}

2.2.4 onFulfilled 或者 onRejected 在执行上下文堆栈仅包含平台代码之前不得调用

nextTick是一个异步执行函数

class Promise {

    // ...

    resolve (value) {
        nextTick(() => {
            // ...
            if (onFulfilledFn) onFulfilledFn(value)
        })
    }
    
    reject (reason) {
        nextTick(() => {
            // ...
            if (onRejectedFn) onRejectedFn(reason)
        })
    }
}

2.2.5 onFulfilled 和 onRejected 必须被作为函数调用(即没有 this 值)


class Promise {

    // ...

    resolve (value) {
        onFulfilledFn.call(undefined, value)
    }
    
    reject (reason) {
        onRejectedFn.call(undefined, reason)
    }
}

2.2.6 then 方法可以被同一个 promise 调用多次

当 promise fulfilled后,所有 onFulfilled 都必须按照其注册顺序执行。

当 promise rejected后,所有 onRejected 都必须按照其注册顺序执行。


class Promise {

    // ...
    callbacks = []
    
    resolve (value) {
        // ...
        this.callbacks.forEach(cb => {
            cb[0].call(undefined, value)
        })
    }
    
    reject (reason) {
        // ...
        this.callbacks.forEach(cb => {
            cb[1].call(undefined, reason)
        })
    }

    then (onFulfilled, onRejected) {
        const callback = []
        if (typeof onFulfilled === 'function') {
            callback[0] = onFulfilled
        }
        if (typeof onRejected === 'function') {
            callback[1] = onRejected
        }
        this.callbacks.push(callback)
    }
}

2.2.7 then 方法必须返回一个 promise 对象

promise2 = promise1.then(onFulfilled, onRejected);
如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:promise2.resolveWith(x)。2.3

如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须被拒绝(rejected)并把e当作拒绝原因(reason)。

如果 onFulfilled 不是函数且 promise1 (fulfilled)成功执行, promise2 必须成功执行并返回相同的值(value)。

如果 onRejected 不是函数且 promise1 (rejected)拒绝执行, promise2 必须拒绝执行并返回相同的拒绝原因(reason)。


class Promise {

    // ...
    
    resolve (value) {
        // ...
        this.callbacks.forEach(cb => {
            try {
                let x
                if (cb[0]){
                    x = cb[0].call(undefined, value)
                } else {
                    x = value
                }
                cb[2].resolveWith.call(cb[2], x)
            } catch (err) {
                cb[2].reject(err)
            }
        })
    }
    
     reject (reason) {
        this.callbacks.forEach(cb => {
            try {
                if (cb[1]) {
                    const x = cb[1].call(undefined, reason)
                    cb[2].resolveWith.call(cb[2], x)
                } else {
                    cb[2].reject(reason)
                }
            } catch (err) {
                cb[2].reject(err)
            }
        })
    }
    
    then (onFulfilled, onRejected) {
        const callback = []
        
        // ...
        
        callback[2] = new Promise(() => {})
        this.callbacks.push(callback)
        return callback[2]
    }
    
    resolveWith (x) {
        // 这里的 this 就是 promise2
        // to do....
    }
}

2.3 Promise 解决过程

2.3.1 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise

resolveWithSelf () {
    this.reject(new TypeError())
}

2.3.2 如果 x 是一个 promise ,采用 promise 的状态

如果 x 处于等待态,promise 需保持为等待态直至 x 被执行或拒绝。

如果 x 处于执行态,用相同的值执行 promise。

如果 x 处于拒绝态,用相同的据因拒绝 promise。

resolveWithPromise (x) {
        x.then(
            value => {
                this.resolveWith(value)
            },
            reason => {
                this.reject(reason)
            }
        )
    }

2.3.3 如果 x 是一个对象或函数

把 x.then 赋值给 then。

如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise。

如果 then 是函数,将 x 作为函数的作用域 this调用之。

传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:

如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)。

如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise。

如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用。

如果调用 then 方法抛出了异常 e:

如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之,否则以 e 为原因拒绝 promise。

如果 then 不是函数,以 x 为参数执行 promise。

resolveWithThenable (x) {
    let called = false
    let then
    try {
        then = x.then
    } catch (err) {
        this.reject(err)
    }
    if (typeof then === 'function') {
        try {
            then.call(
                x,
                y => {
                    if (called) return
                    called = true
                    this.resolveWith(y)
                },
                r => {
                    if (called) return
                    called = true
                    this.reject(r)
                }
            )
        } catch (err) {
            if (called) return
            called = true
            this.reject(err)
        }
    } else {
        this.resolve(x)
    }
}

2.3.4 如果 x 不为对象或者函数,以 x 为参数执行 promise

resolveWith (x) {
    if (this === x) {
        this.resolveWithSelf()
    } else if (x instanceof Promise) {
        this.resolveWithPromise(x)
    } else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        this.resolveWithThenable(x)
    } else {
        this.resolve(x)
    }
}

3. Promise 完整代码实现

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

let nextTick

if (typeof process !== 'undefined' && typeof process.nextTick === 'function') {
    nextTick = (fn) => {
        process.nextTick(fn)
    }
} else if (typeof MutationObserver !== 'undefined') {
    let counter = 1
    nextTick = (fn) => {
        const observer = new MutationObserver(fn)
        const textNode = document.createTextNode(String(counter))
        observer.observe(textNode, {
            characterData: true
        })
        counter = counter + 1
        textNode.data = String(counter)
    }
}

class Promise {

    state = PENDING
    value = undefined
    reason = undefined
    callbacks = []

    constructor (fn) {
        if (typeof fn !== 'function') {
            throw new Error(`Promise resolver ${fn} is not a function`)
        }

        fn(this.resolve.bind(this), this.reject.bind(this))
    }

    resolve (value) {
        nextTick(() => {
            if (this.state !== PENDING) return
            this.state = FULFILLED
            this.value = value
            this.callbacks.forEach(cb => {
                try {
                    let res
                    if (cb[0]){
                        res = cb[0].call(undefined, value)
                    } else {
                        // 2.2.7.3  onFulfilled不是一个函数时 value要传给下一个then
                        res = value
                    }
                    cb[2].resolveWith.call(cb[2], res)
                } catch (err) {
                    cb[2].reject(err)
                }
            })
        })
    }

    reject (reason) {
        nextTick(() => {
            if (this.state !== PENDING) return
            this.state = REJECTED
            this.reason = reason
            this.callbacks.forEach(cb => {
                try {
                    if (cb[1]) {
                        const res = cb[1].call(undefined, reason)
                        cb[2].resolveWith.call(cb[2], res)
                    } else {
                        cb[2].reject(reason)
                    }
                } catch (err) {
                    cb[2].reject(err)
                }
            })
        })
    }

    then (onFulfilled, onRejected) {
        const callback = []
        if (typeof onFulfilled === 'function') {
            if (this.state === FULFILLED) {
                // 2.2.4 promise.then中嵌套promise.then,如果已经 fulfilled 就直接调用onFulfilled
                // 因为在当前tick中,嵌套的promise.then还未存放到callbacks里面
                nextTick(() => {
                    const value = onFulfilled.call(undefined, this.value)
                    // 处理后续
                    callback[2].resolveWith.call(callback[2], value)
                })
            } else {
                callback[0] = onFulfilled
            }
        }
        if (typeof onRejected === 'function') {
            if (this.state === REJECTED) {
                // 2.2.4 promise.then中嵌套promise.then,如果已经 rejected 就直接调用 onRejected
                // 因为在当前tick中,嵌套的promise.then还未存放到callbacks里面
                nextTick(() => {
                    const value = onRejected.call(undefined, this.reason)
                    // 处理后续
                    callback[2].resolveWith.call(callback[2], value)
                })
            } else {
                callback[1] = onRejected
            }
        }
        callback[2] = new Promise(() => {})
        this.callbacks.push(callback)
        return callback[2]
    }

    resolveWithSelf () {
        this.reject(new TypeError())
    }

    resolveWithPromise (x) {
        x.then(
            value => {
                this.resolveWith(value)
            },
            reason => {
                this.reject(reason)
            }
        )
    }

    resolveWithThenable (x) {
        let called = false
        let then
        try {
            then = x.then
        } catch (err) {
            this.reject(err)
        }
        if (typeof then === 'function') {
            try {
                then.call(
                    x,
                    y => {
                        // 递归处理thenable,处理完了this.resolve才是出口
                        if (called) return
                        called = true
                        this.resolveWith(y)
                    },
                    r => {
                        if (called) return
                        called = true
                        this.reject(r)
                    }
                )
            } catch (err) {
                if (called) return
                called = true
                this.reject(err)
            }
        } else {
            this.resolve(x)
        }
    }

    resolveWith (x) {
        if (this === x) {
            this.resolveWithSelf()
        } else if (x instanceof Promise) {
            this.resolveWithPromise(x)
        } else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
            this.resolveWithThenable(x)
        } else {
            this.resolve(x)
        }
    }
}

github源码地址:https://github.com/xingxinglail/promise

4. 参考资料

Promises/A+规范

【翻译】Promises/A+规范

【翻译】Promises/A+规范

【MDN】MutationObserver