Day6 - 实现一个自己的Promise(完整版)

485 阅读6分钟

前言

网上已经有很多前辈写过该如何一步步的实现一个符合Promise/A+规范的文章了

但是呢

不管看多少篇、多少遍,真的不如自己对照规范一行一行手动敲一遍来的实在~ 😂😂😂

建议是多看几遍规范,认真理解之后再开始一个功能一个功能的实现~

封面来源——ivanjov.com

参考

阮一峰老师的文章 Promise对象

Promises/A+规范原文

【翻译】Promises/A+规范

完整版

/**
 * 根据Promise/A+规范,实现自己的Promise
 * 
 * Promise 表示一个异步操作的最终结果,与之进行交互的方式主要是 then 方法,
 * 该方法注册了两个回调函数,用于接收 promise 的终值或本 promise 不能执行的原因。
 * 
 * 一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)
 * 并且只能由Pending -> Fulfilled 或者 Pending -> Rejected,且必须拥有一个不可变的终值或拒因
 * 
 * 一个 promise 必须提供一个 then 方法以访问其当前值、终值和据因。
 */

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

function Promise (excutor) {
    const that = this
    that.status = PENDING
    that.value = undefined
    that.reason = undefined
    // 存储fulFilled状态对应的onFulfilled函数
    that.onFulfilledCallbacks = []
    // 存储rejected状态对应的onRejected函数
    that.onRejectedCallbacks = []

    /**
     * @param {*} value 成功态接收的终值
     * 
     * 为什么resolve 加setTimeout?
     * 一 2.2.4规范 要确保 onFulfilled 和 onRejected 方法异步执行 
     * (且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行) 所以要在resolve里加上setTimeout
     * 
     * 二 2.2.6规范 对于一个promise,它的then方法可以调用多次.(当在其他程序中多次调用同一个promise的then时 
     * 由于之前状态已经为FULFILLED/REJECTED状态,则会走的下面逻辑),所以要确保为FULFILLED/REJECTED状态后 也要异步执行onFulfilled/onRejected
     * 
     * onFulfilled 和 onRejected 必须被作为函数调用(即没有 this 值),且只允许在执行环境堆栈仅包含平台代码时运行
     * 对应规范中 2.2.4 
     * 
     * 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 
     * 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
     */
    function resolve (value) {
        // 解决resolve方法嵌套返回promise的问题
        if (value instanceof Promise) {
            return value.then(resolve, reject)
        }
        setTimeout(() => {
            if (that.status === PENDING) {
                // 只能由 pending -> fulfilled状态 (避免调用多次resolve reject)
                that.status = FULFILLED
                that.value = value
                // 分别执行成功状态订阅器中的回调方法
                that.onFulfilledCallbacks.forEach(cb => cb(that.value))
            }
        })
    }

    /**
     * 为什么reject中不用判断reason类型?
     * @param {*} reason 失败态接收到拒因
     */
    function reject (reason) {
        setTimeout(() => {
            if (that.status === PENDING) {
                // 只能由 pending -> rejected状态 (避免调用多次resolve reject)
                that.status = REJECTED
                that.reason = reason
                // 分别执行订失败状态阅器中的回调方法
                that.onRejectedCallbacks.forEach(cb => cb(that.reason))
            }
        })
    }

    // 捕获excutor执行器中的异常
    try{
        excutor(resolve, reject)
    } catch (err) {
        reject(err)
    }
}

/**
 * 注册fulfilled状态/rejected状态的回调函数
 * @param {Function} onFulfilled    fulfilled状态执行的函数
 * @param {Function} onRejected    rejected状态执行的函数
 * @returns {Function} newPromise   返回一个新的promised
 */
Promise.prototype.then = function (onFulfilled, onRejected) {
    /**
     * 处理参数默认值,保证后续可以继续执行
     * 对应规范中 2.2.1 如果 onFulfilled / onRejected 不是函数,其必须被忽略
     * 
     */
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {
        throw reason;
    };
    
    /**
     * then里面的FULFILLED/REJECTED状态时, 为什么要加setTimeout?
     * 
     */
    let that = this
    let promise2
    if (that.status === FULFILLED) {
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try{
                    let x = onFulfilled(that.value)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (err) {
                    reject(err)
                }
            })
        })
    }

    if (that.status === REJECTED) {
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try{
                    let x = onRejected(that.reason)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (err) {
                    reject(err)
                }
            })
        })
    } 
    
    if (that.status === PENDING) {
        // 这里是为了解决异步的问题,采用发布订阅的方式,下面两个数组分别存储成功和失败的回调
        // 返回一个Promise是为了解决可以链式调用的问题

        return promise2 = new Promise((resolve, reject) => {
            that.onFulfilledCallbacks.push((value) => {
                try {
                    let x = onFulfilled(value)
                    // 解析promise流程
                    resolvePromise(promise2, x, resolve, reject)
                } catch (err) {
                    reject(err)
                }
            })

            that.onRejectedCallbacks.push((reason) => {
                try {
                    let x = onRejected(reason)
                    // 解析promise流程
                    resolvePromise(promise2, x, resolve, reject)
                } catch (err) {
                    reject(err)
                }
            })

        })
    }
}

function resolvePromise (promise2, x, resolve, reject) {
    // console.log(this)
    // 如果 promise 和 x 指向同一对象,会导致循环引用报错,So 以 TypeError 为据因拒绝执行 promise
    // 对应规范中 2.3.1
    if (promise2 === x) {
        return reject(new TypeError('循环引用'))
    }
    // promise2是否已经resolve或者reject,避免重复调用
    let called = false
    // 如果x是一个promise对象,继续resolve
    // 对应规范中 2.3.2
    if (x instanceof Promise) {
        // 如果是等待状态,则需要保持等待态直至 x 被执行 / 被拒绝,并解析y值
        // 对应规范中 2.3.2.1
        if (x.status === PENDING) {
            x.then(y => {
                resolvePromise(promise2, y, resolve, reject)
            }, reason => {
                reject(reason)
            })
        } else {
            // 如果x已经处于执行态 / 拒绝态,则用相同的值 / 拒因 执行promise
            // 对应规范中 2.3.2.2 和 2.3.2.3
            x.then(resolve, reject)
        }
        // 如果 x 为对象或者函数
        // 对应规范中 2.3.3
    } else if (x !== null && ((typeof x === 'object') || (typeof x === 'function'))) {
        try {
            /**
             * 这步我们先是存储了一个指向 x.then 的引用,然后测试并调用该引用,以避免多次访问 x.then 属性。
             * 这种预防措施确保了该属性的一致性,因为其值可能在检索调用时被改变。
             * 对应规范中 2.3.3.1
             */
            let then = x.then

            /**
             * 如果 then 是函数,将 x 作为函数的作用域 this 调用之。
             * 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
             * 对应规范中 2.3.3.3
             */
            if (typeof then === 'function') {
                then.call(x, y => {
                    /**
                     * 如果 resolvePromise 和 rejectPromise 均被调用,
                     * 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
                     * 对应规范中 2.3.3.3.3
                     */
                    if (called) return
                    called = true

                    // 对应规范中 2.3.3.3.1
                    resolvePromise(promise2, y, resolve, reject)
                }, reason => {
                    if (called) return
                    called = true

                    // 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
                    // 对应规范中 2.3.3.3.2
                    reject(reason)
                })
            } else {
                // 如果 then 不是函数,以 x 为参数执行 promise
                // 对应规范中 2.3.3.4
                resolve(x)
            }
        } catch (e) {
            // 如果调用 then 方法抛出了异常 e
            // 对应规范中 2.3.3.3.4

            // 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
            // 对应规范中 2.3.3.3.4.1
            if (called) return
            called = true

            // 否则以 e 为据因拒绝 promise
            // 对应规范中 2.3.3.3.4.2
            reject(e)
        }
    } else {
        // 如果 then 不是函数,是一个普通的值,以 x 为参数执行 promise
        // 对应规范中 2.3.4
        resolve(x)
    }
}

测试

// 在promise实现的代码中,增加以下执行测试用例需要用到的代码
Promise.deferred = function() {
    let defer = {};
    defer.promise = new Promise((resolve, reject) => {
        defer.resolve = resolve;
        defer.reject = reject;
    });
    return defer;
}

try {
    module.exports = Promise
} catch (e) {
    console.log(e, '---')
}

// 安装测试脚本
npm i -g promises-aplus-tests

// 测试命令
promises-aplus-tests Promise.js

// 872 passing

Promise其他方法

// 立刻返回一个promise,一般用于没有promise对象,需要将一个东西,转为promise
Promise.resolve = function (data) {
    return new Promise(resolve => {
        resolve(data)
    })
}

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


// 接收一个promise数组,全部成功之后才往下执行,并返回一个promise
Promise.all = function (promiseArray) {
    return new Promise((resolve, reject) => {
        let resolveArr = []
        promiseArray.forEach(item => {
            item.then(data => {
                resolveArr.push(data)
                console.log(data, '---data')
                if (promiseArray.length === resolveArr.length) {
                    resolve(resolveArr)
                }
            }, reason => {
                reject(reason)
            })
        })
    })
}


// 接收一个promise数组,只要有一个先返回,无论是resolve还是reject,都会往下执行then中的成功或者失败回调,
// 其他的promise也会继续执行,但是不会使用结果
Promise.race = function (promiseArray) {
    return new Promise((resolve, reject) => {
        promiseArray.forEach(item => {
            item.then(data => {
                resolve(data)
            }, reason => {
                reject(reason)
            })
        })
    })
}


// 用于捕获错误的回调,即第一个resolve参数为null的特殊then方法
Promise.prototype.catch = function (reject) {
    return this.then(null, reject)
}


// 无论前面执行结果状态,都会进入该方法中,且会将值原封不动的传给后面的then
Promise.prototype.finally = function (callback) {
    return this.then(value => {
        return new Promise(callback()).then(() => {
            return value
        })
    }, reason => {
        return new Promise(callback()).then(() => {
            throw reason
        })
    })
}