手写基于A+规范的Promise

手写基于A+规范的Promise

一、注释版

const $Promise = (function () {
        function $Promise(executor) {
            abnormalCheck.isFunction(executor);
            createPromise(this);
            let returnValue = executeFn(executor,result => {changePromise(this,'fulfilled',result)},result => {changePromise(this,'rejected',result)});
            returnValue instanceof Error && changePromise(this,'rejected',returnValue);
        }
        /*$Promise
        * 1、判断是否为函数,如果不是,直接报错
        * 2、创建 promise,这里没有采用构造函数创建对象的方式,采用的是构造函数调用其他函数来创建对象的方式,这里的 this 就是 new 执行时产生的新对象,
        * 这么做的原因是,创建 promise 的位置很多,对于有些地方,构造函数中有些代码时多余的,为了提升性能,也为了更好的复用,于是采用了这种设计
        * 3、执行 executor函数,在执行时,还要检测是否会报错,所以使用了带有检测功能的 executeFn 函数来执行 executor 函数,并且还需要传递两个函数给 executor 函数,
        * 调用这两个函数,将改变 promise 的状态,这里对原有 changePromise 函数进行了封装,目的是提前传参给它,并且要求这个函数是无返回的,所以在函数体加上了{},
        * 这里也可以用 bind 替换箭头函数,但性能会有所下降
        * 4、通过 executeFn 函数的返回值,判断是否在执行 executor 函数时存在异常,如果存在异常,更改 promise 的状态
        * */
        $Promise.prototype = {
            constructor: $Promise,
            /*原本原型对象会随函数一起创建,并且还有一个默认属性 constructor,由于这里将函数 prototype 重新指向了一个新对象,为了保证新原型对象的完整性,添加了这条语句*/
            then (onResolved,onRejected) {
                let nextPromise = createPromise({},'pending');
                this[propertyName.nextPromiseQueue].push({nextPromise,onResolved,onRejected});
                return nextPromise;
            },
            /*then
            * 1、创建一个 promise
            * 2、将新对象和 onResolved,onRejected 保存到上一个 promise 中,为了状态改变调用
            * 3、返回新的 promise
            * 后续的工作交给 changePromise 函数
            * */
            catch (onRejected) {
                return this.then(null,onRejected);
            },
            /*catch 方法可以简单实现为 then 的语法糖*/
            finally (onFinally) {
                return this.then(result => {
                    onFinally();
                    return result;
                },error => {
                    onFinally();           
                    throw error;
                });
            }
            /*finally
            * finally 方法也可以通过 then 方法实现,给then传递两个函数,两个函数中都执行了 onFinally,保证 onFinally 一定会执行,并通过返回值,
            * 实现了返回和上次类似的 promise
            * 这里有一个注意点,一般情况来说,返回的 promise 和原来的 promise 长相相同,
            * 但如果在执行函数时发生错误,则会返回 promise 的状态将是自身的错误
            * 基于上面的特殊情况,进行了处理,这里的语句 throw error 一般情况下,将抛出上个 promise 的错误,让新的 promise 去接收,去更改状态,
            * 由于在执行任何用户输入的函数,都利用了带有检测功能的 executeFn 函数来执行,所以如果函数自身有错,会优先使用自己的错误信息
            * */
        }
        $Promise.resolve = function (result) {
            return createPromise({},'fulfilled',result);
        }
        /*resolve 方法实际上就创建一个 promise*/
        $Promise.reject = function (result) {
            return createPromise({},'rejected',result);
        }
        /*reject 方法跟 resolve 方法相同*/
        $Promise.all = function (promiseArr) {
            let nextPromise = createPromise({},'pending'),
                resultArr = Array(promiseArr.length),
                amount = 0;
            promiseArr.forEach((promise,index) => {
                if (promise instanceof $Promise && promise[propertyName.state] === 'pending') {      //处理待更新的 promise
                    promise.then(result => {
                        resultArr[index] = result;
                    },error => {
                        changePromise(nextPromise,'rejected',error);
                    })
                } else if (promise instanceof $Promise) {     //处理更新过的 promise
                    resultArr[index] = promise[propertyName.state] === 'fulfilled' ?
                        promise[propertyName.result] : changePromise(nextPromise,'rejected',promise[propertyName.result]);
                } else {     //处理非 promise
                    resultArr[index] = promise;
                }
                ++amount === promiseArr.length && changePromise(nextPromise,'fulfilled',resultArr);
            })
            return nextPromise;
        }
        /*all
        * 1、创建一个 promise 
        * 2、根据所有传入 promise 的状态,更新新的 promise 
        * 3、返回这个 promise 
        * 重点是第二步,如何监听到 promise 队列中每一个状态变化?
        * 方法是在每一个 promise 上加入一个 then,只要 promise 状态一变,就会执行then中的函数,
        * 执行函数时,首先要将 result 插入到 resultArr 的指定位置,然后进行判断插入的数量 amount 是否等于 promise 队列的长度,如果相等证明全部运行完,
        * 最后通过 changePromise 函数更改新 promise 状态
        * 需要注意的是,all 接收的数组中,不止可以有待更新的 promise,还可能有更新过的 promise,或者非 promise,
        * 于是在原本基础上加上了 if-else 语句,用来处理这两种特殊情况
        * */
        function dell(obj,state,result) {
            let [nextState,nextResult] = state === 'rejected' && obj.onRejected || state === 'fulfilled' && obj.onResolved ?
                (() => {
                    let returnValue = state === 'fulfilled' ? executeFn(obj.onResolved,result) : executeFn(obj.onRejected,result);
                    if (returnValue instanceof $Promise) {
                        return [returnValue[propertyName.state],returnValue[propertyName.result]];
                    } else if (returnValue instanceof Error) {
                        return ['rejected',returnValue];
                    } else {
                        return ['fulfilled',returnValue];
                    }
                })() : [state,result];
            /*
            let returnValue,nextState,nextResult;
            if (state === 'rejected' && obj.onRejected || state === 'fulfilled' && obj.onResolved) {
                returnValue = state === 'fulfilled' ? executeFn(obj.onResolved,result) : executeFn(obj.onRejected,result);
                if (returnValue instanceof $Promise) {
                    [nextState,nextResult] = [returnValue[propertyName.state],returnValue[propertyName.result]];
                } else if (returnValue instanceof Error) {
                    [nextState,nextResult] = ['rejected',returnValue];
                } else {
                    [nextState,nextResult] = ['fulfilled',returnValue];
                }
            } else {
                [nextState,nextResult] = [state,result];
            }*/
            changePromise(obj.nextPromise,nextState,nextResult);
        }
        /*dell
        * 核心处理函数,根据上一个的状态和 onResolved, onRejected,计算出下一个 promise 的状态,然后执行 changePromise 函数,让它做更改
        * dell 和 changePromise 的关系就像是节点和树枝,一个节点可以有多个树枝,树枝的末尾可以有一个新的节点,changePromise 函数就相当于节点
        * 这部分用了两种代码实现的,被注释的版本更好理解,没被注释的版本可读性比较差,但性能会更加优秀
        * 消耗一定的资源,所以减少语句有助于性能的提升
        * */
        function executeFn(fn,...params) {
            try {return fn(...params)}
            catch (error){return error}
        }
        /*executeFn
        * 执行函数的函数,所有用户传入的函数,都将使用这个函数执行
        * 如果没报错,正常返回,如果报错了,返回错误,调用者都会判断这个返回是否是错误
        * */
        function createPromise (promise,state,result){
            [promise[propertyName.state],promise[propertyName.result]] = [state || 'pending',result];
            promise[propertyName.nextPromiseQueue] = [];
            promise.__proto__ = $Promise.prototype;
            return promise;
        }
        /*createPromise
        * 只用来创建 promise 对象
        * 1、在空对象上加入 promise 应该有的属性,state 和 result,还有一个用于保存下一个 promise 和 onResolved, onRejected 的数组
        * 2、由于原本的空对象的 __proto__ 指向的是 Object.prototype,需要修改为 $Promise.prototype,保证原型链的完整
        * 3、返回这个 promise,这里手动传入一个对象(一般是空对象)和给返回的原因是为了方便部分代码的编写,例如 resolve 和 reject
        * */
        function changePromise (promise,state,result){
            if(promise[propertyName.state] === 'pending'){
                [promise[propertyName.state],promise[propertyName.result]] = [state,result];
                setTimeout(() => {
                    abnormalCheck.hasRejectedDeal(promise);
                    promise[propertyName.nextPromiseQueue].forEach(obj => dell(obj,state,result))
                },0);
            }
        }
        /*changePromise
        * 通过这个函数更新 promise
        * 通过传入的参数,决定 promise 的最终状态,然后需要执行后续的 then 方法中的 onResolved, onRejected,通过函数的执行来更新下一个 promise
        * 原本 onResolved, onRejected 在执行代码时,不会立即执行,而是放在微任务队列,等待宏任务执行结束,再去执行
        * 因为无法实现微任务,所以采用了宏任务代替微任务
        * 宏任务模拟微任务存在的问题:
        * 微任务队列会一次执行完,而宏任务队列不会,每次只能执行一个宏任务,然后执行微任务队列和渲染页面,所以会存在时间上的误差
        * 注意点:这里的 0s 后执行,如果不考虑硬件和操作系统的因素,理论上是可以做到的,根据HTML5标准,经过5重嵌套定时器之后,时间间隔被强制设定为至少4毫秒,
        * 这里没有采用嵌套,所以它也可以立即执行
        * 通过 abnormalCheck.hasRejectedDeal 方法可以检测是否有后续的 promise,如果没有并且当前 promise 还为 'rejected',将会报错
        * 这样就实现了一条链上多个'rejected',最终只抛出一个错误的效果
        * 最后就是遍历缓存的 promise 队列,通知他们需要执行 onResolved, onRejected,通过 dell 函数
        * */
        const propertyName = {
            state: Symbol('State'),
            result: Symbol('Result'),
            nextPromiseQueue: Symbol('NextPromiseQueue')
        }
        /*为了防止 promise 属性不小心被修改,使用了局部 Symbol 作为属性名,这只能防止不小心,如果真想修改,还是有办法的*/
        const abnormalCheck = {
            isFunction (fn){    //判断是否是函数
                if (typeof fn !== "function") {
                    let error = new Error("Promise resolver undefined is not a function");
                    error.name = "TypeError";
                    throw error;
                }
            },
            hasRejectedDeal (promise){  //如果一个状态为 rejected 的 promise,并且后续没有后续 promise,将会报错
                promise[propertyName.state] === 'rejected'
                && !promise[propertyName.nextPromiseQueue].length
                && console.error("Uncaught (in promise) " + promise[propertyName.result]);
            }
        }
        /*abnormalCheck 异常检测对象,为了更好的复用,所以单独提取*/
        return $Promise;
        /*使用闭包进行返回,保证局部不会污染全局*/
    })()
//export default $Promise		//提供ES6导出

二、无注释版

const $Promise = (function () {
        function $Promise(executor) {
            abnormalCheck.isFunction(executor);
            createPromise(this);
            let returnValue = executeFn(executor,result => {changePromise(this,'fulfilled',result)},result => {changePromise(this,'rejected',result)});
            returnValue instanceof Error && changePromise(this,'rejected',returnValue);
        }
        $Promise.prototype = {
            constructor: $Promise,
            then (onResolved,onRejected) {
                let nextPromise = createPromise({},'pending');
                this[propertyName.nextPromiseQueue].push({nextPromise,onResolved,onRejected});
                return nextPromise;
            },
            catch (onRejected) {
                return this.then(null,onRejected);
            },
            finally (onFinally) {
                return this.then(result => {
                    onFinally();
                    return result;
                },error => {
                    onFinally();           
                    throw error;
                });
            }
        }
        $Promise.resolve = function (result) {
            return createPromise({},'fulfilled',result);
        }
        $Promise.reject = function (result) {
            return createPromise({},'rejected',result);
        }
        $Promise.all = function (promiseArr) {
            let nextPromise = createPromise({},'pending'),
                resultArr = Array(promiseArr.length),
                amount = 0;
            promiseArr.forEach((promise,index) => {
                if (promise instanceof $Promise && promise[propertyName.state] === 'pending') {      
                    promise.then(result => {
                        resultArr[index] = result;
                    },error => {
                        changePromise(nextPromise,'rejected',error);
                    })
                } else if (promise instanceof $Promise) {     
                    resultArr[index] = promise[propertyName.state] === 'fulfilled' ?
                        promise[propertyName.result] : changePromise(nextPromise,'rejected',promise[propertyName.result]);
                } else {     
                    resultArr[index] = promise;
                }
                ++amount === promiseArr.length && changePromise(nextPromise,'fulfilled',resultArr);
            })
            return nextPromise;
        }
        function dell(obj,state,result) {
            let [nextState,nextResult] = state === 'rejected' && obj.onRejected || state === 'fulfilled' && obj.onResolved ?
                (() => {
                    let returnValue = state === 'fulfilled' ? executeFn(obj.onResolved,result) : executeFn(obj.onRejected,result);
                    if (returnValue instanceof $Promise) {
                        return [returnValue[propertyName.state],returnValue[propertyName.result]];
                    } else if (returnValue instanceof Error) {
                        return ['rejected',returnValue];
                    } else {
                        return ['fulfilled',returnValue];
                    }
                })() : [state,result];
            changePromise(obj.nextPromise,nextState,nextResult);
        }
        function executeFn(fn,...params) {
            try {return fn(...params)}
            catch (error){return error}
        }
        function createPromise (promise,state,result){
            [promise[propertyName.state],promise[propertyName.result]] = [state || 'pending',result];
            promise[propertyName.nextPromiseQueue] = [];
            promise.__proto__ = $Promise.prototype;
            return promise;
        }
        function changePromise (promise,state,result){
            if(promise[propertyName.state] === 'pending'){
                [promise[propertyName.state],promise[propertyName.result]] = [state,result];
                setTimeout(() => {
                    abnormalCheck.hasRejectedDeal(promise);
                    promise[propertyName.nextPromiseQueue].forEach(obj => dell(obj,state,result))
                },0);
            }
        }
        const propertyName = {
            state: Symbol('State'),
            result: Symbol('Result'),
            nextPromiseQueue: Symbol('NextPromiseQueue')
        }
        const abnormalCheck = {
            isFunction (fn){    
                if (typeof fn !== "function") {
                    let error = new Error("Promise resolver undefined is not a function");
                    error.name = "TypeError";
                    throw error;
                }
            },
            hasRejectedDeal (promise){  
                promise[propertyName.state] === 'rejected'
                && !promise[propertyName.nextPromiseQueue].length
                && console.error("Uncaught (in promise) " + promise[propertyName.result]);
            }
        }
        return $Promise;
    })()
//export default $Promise
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值