面试必会——手撸promise(超详细)

前几天翻译了promise/A+的规范,感觉自己又行了,所以试一下手撸一下promise,目标两点:

  1. 完全符合promise/A+规范
  2. 实现的promise能够通过官方的测试case

开始之前多嘱咐一句,不管是阅读本文,还是想自己手撸promise,首先一定要熟悉promise/A+规范的内容,不了解的同学,可以查看这里 Promises/A+ 规范(译文)

1. 初步实现

1.1 promise的属性

作为使用过promise的同学(当然了,如果没使用过,可以先去看看 es6 Promise 对象),应该知道promise包含有以下属性和方法:

  • 属性
    • promiseStatepromise的状态
    • promiseResult :当前promise的结果,比如resolvevaluerejectreason
    • microTaskQuene : 存储当前promise相关的微任务队列,看网上一些资料,有的实现会设置两个数组,分别来存储resolvereject,本质是一个意思(可能没有动手实现过的同学,到这就有点上强度了,但是不要怕,后续会有它的更详细的介绍)
  • 方法
    • resolve : 要和new Promise((resolve, reject) => {})中的resolve做一个区分,new Promise((resolve, reject) => {})中的resolve只是一个形参,换成任何一个合法的函数名字均可,这里的 resolve 单指Promise上的方法, 主要作用是返回一个状态被设置为fulfilledpromise对象,后面再展开
    • reject : 注意事项同上,作用是返回一个状态被设置为rejectedpromise对象
    • then :【原型方法】 promise状态被设置为fulfilled的回调函数
    • catch : 【原型方法】 promise状态被设置为rejected的回调函数
    • finally : 【原型方法】 promise不管状态被设置为谁,最终一定会执行的回调函数
    • all: 将多个promise包装成一个promise,只有都执行成功 或者任何一个失败才会调用
    • race: 将多个promise包装成一个promise任何一个异步改变状态(注意,这里是不论成功失败)就会调用
    • allSettled: 将多个promise包装成一个promise,只有所有的异步均执行结束才会调用,主要和all的区别是,当有异步失败的情况下,allSettled也必须等到所有都执行完成才会执行
    • any: 这里是和race对比,当有任何一个异步成功,即为成功,区别是,必须所有的异步都失败any才会置成失败
    • try: 提案阶段,来解决promise无法捕获异步异常的问题

看到这,不要怕,上面只是对于promise对象的整理和简述,让大家有一个初步的认识,感觉有点模糊还是要翻翻阮一峰的文档(我写上面还是又翻了一遍,确实细节多,一次一个收获):

这里的图中也是对应上面加粗的是promise核心的方法和属性,也是我们本次手撸的重点对象,其他的几个方法,后续看情况要不要实现。

1.2 代码结构

到这里应该大致知道了什么是promise,算是promise大致的框架,到这里,我们就可以写出promise的大致的框架:

javascript 复制代码
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
    #promiseState = PENDING;
    #promiseResult = undefined;

    constructor(executor) {
        // 注意和下面的#resolve和#reject方法区别
        let resolve = (value) => {}
        let reject = (reason) => {}
        executor(resolve, reject)
    }

    then(){}
    catch(){}
    finally(){}

    // 下面是在Promise类上面的方法
    static resolve(value){}
    static reject(reason){}
    static race([]){}
    static all([]){}
    static allSettled([]){}
    static try(f){}

}

我们这里使用了class来定义promisepromise/A+规范来说,只要是实现了then方法的对象或者函数均能称为promise,所以同学应该也能从网上function的版本。使用class的话,使用静态方法和私有属性,看起来更优雅。

上面有几点需要注意:

  1. promise的状态有3种: pending,fulfilled,rejected,所以我们定义三个常量
  2. 如果我们打印promise 对象,可以看到其实有两个属性promiseStatepromiseResult,其中promiseState的初始状态为pending,promiseResult初始值为undefined,同时因为我们不希望除了内部的resolve和reject方法修改这两个值,所以我们将这两个属性设置为class的私有属性
  3. 我们let p = new Promise((resolve, reject) => {})之后打印p可以看到只有thencatchfinally方法,其他的几个方法只能通过类Promise进行调用,所以正好符合class的静态方法

ok,到这里基本有了promise的基本结构啦,下面开始具体的实现:

1.3 constructor方法

我们就自上而下开始写吧,首先看一下这个class的构造方法怎么实现,这里我们首先要想到我们是怎么使用promise:

javascript 复制代码
let p = new Promise((resolve, reject) => {
    // 干点啥
})

所以可以知道promise的构造函数的参数是一个方法,方法的参数是两个方法resolve和reject,而这两个方法的主要作用是更新我们promise的状态,所以具体实现如下:

javascript 复制代码
constructor(executor) {
    let myResolve = (value) => {
        if (this.#promiseState === PENDING) {
            this.#promiseState = FULFILLED;
            this.#promiseResult = value;
            // todo 这里有伏笔
        }
    }
    let myReject = (reason) => {
        if (this.#promiseState === PENDING) {
            this.#promiseState = REJECTED;
            this.#promiseResult = reason;
            // todo 这里有伏笔
        }
    }
    try {
        executor(myResolve, myReject);
    } catch (e) {
        this.#promiseState = REJECTED;
        this.#promiseResult = e;
    }
}

上面的代码有几点需要注意:

  1. 上面其实也提到了,我们这里executor的参数名字 只是一个形参 ,叫啥都可以,大家千万不要和Promise.resolvePromise.reject混淆了,我这里也是特意换了一个名字做了区分(不瞒大家,我最开始就以为他俩是一个东西,谁知道真不是个东西!)
  2. promise的特性是
    2.1 状态一旦变更,就不会再发生改变
    2.2 且只有pending状态下的promise可以被改变状态,所以我们做了状态的判断和限制
  3. 在执行executor时可能会抛出异常,一旦抛出来异常,promise的状态也要设置为reject(这一点也是容易疏漏的)

1.3.1 遗留的问题

  1. 这里的 myReject和myReject是不是写完整了,很显然没有,我在对应的位置留下了todo,在后面会解答,大家可以先思考一下
  2. resolve的方法,当我们进行如下调用:
javascript 复制代码
let p = new MyPromise((resolve, reject) => {
    resolve({
        then: (resolve2, reject2) => {
            reject2(2);
        }
    })
})

console.log(p);

按照我们上面的写法,最终会返回一个promise,状态是是fulfilled,value是我们传入的对象:

javascript 复制代码
MyPromise {
  status: 'fulfilled',
  value: { then: [Function: then] }
}

但是实际上promise的表现为:

javascript 复制代码
let p = new Promise((resolve, reject) => {
    resolve({
        then: (resolve2, reject2) => {
            reject2(2);
        }
    })
})

console.log(p);

//这里打印成pending的原因涉及到promise的执行
// 可以理解为then方法在下次循环才会执行

// Promise { <pending> }

// 所以
p.then(res => {
    console.log('res', res);
}).catch(e => {
    console.log('catch', e);
});

// catch 2

这里大家姑且先这么看着,在下面讲到resolve方法的时候,再详细展开

1.4 then 方法

上面也提到了,只要是实现了then方法的对象或者函数,均可称为promise,可见then在promise中的位置之重。而promise/A+规范中基本就是在指导我们如何实现一个then方法,我们接下来也是按照promise/A+规范一步步的来实现then方法:

注意:这里因为规范有些细节写的不是很明确,所以我们同时会参考规范对应的测试case进行逆向分析

首先then方法的语法如下:

javascript 复制代码
promise.then(onFulfilled, onRejected)

1.4.1 规范「2.2.1」

onFulfilledonRejected都是可选的参数:

如果 onFulfilled 的类型不是函数,那它将一定被忽略

如果 onRejected 的类型不是函数,那它将一定被忽略

所以这个规则比较好理解,但是不好实现,因为单从规范上我们看不出什么是被忽略,我最开始理解的是这样的:

javascript 复制代码
then(onFulfilled, onRejected) {
    if (typeof onFulfilled !== 'function') {
        return;
    }
    if (typeof onRejected !== 'function') {
        return;
    }
}

但是显然不是这样的,如果这么干,下面就会报错了

scss 复制代码
p = Promise.resolve().then(undefine, undefine);
// 显然这里的p是undefine
p.then(() => {}, () =>)

所以我们要看一下针对这个规则,测试case是怎么写的,来看一下:

scss 复制代码
describe("applied to a directly-rejected promise", function () {
    function testNonFunction(nonFunction, stringRepresentation) {
        specify("`onFulfilled` is " + stringRepresentation, function (done) {
            //
            rejected(dummy).then(nonFunction, function () {
                done();
            });
        });
    }
    // 当 onFulfilled 是非函数的情况,不会影响onRejected的执行
    testNonFunction(undefined, "`undefined`");
    testNonFunction(null, "`null`");
    testNonFunction(false, "`false`");
    testNonFunction(5, "`5`");
    testNonFunction({}, "an object");
});

describe("applied to a promise rejected and then chained off of", function () {
    function testNonFunction(nonFunction, stringRepresentation) {
        specify("`onFulfilled` is " + stringRepresentation, function (done) {
            rejected(dummy).then(function () { }, undefined).then(nonFunction, function () {
                done();
            });
        });
    }
    // 当onRejected 是非函数的情况,不会影响then之后的链式调用
    testNonFunction(undefined, "`undefined`");
    testNonFunction(null, "`null`");
    testNonFunction(false, "`false`");
    testNonFunction(5, "`5`");
    testNonFunction({}, "an object");
});
describe("applied to a directly-fulfilled promise", function () {
    function testNonFunction(nonFunction, stringRepresentation) {
        specify("`onRejected` is " + stringRepresentation, function (done) {
            resolved(dummy).then(function () {
                done();
            }, nonFunction);
        });
    }
    // 当 onRejected 是非函数的情况,不会影响 onFulfilled 的执行
    testNonFunction(undefined, "`undefined`");
    testNonFunction(null, "`null`");
    testNonFunction(false, "`false`");
    testNonFunction(5, "`5`");
    testNonFunction({}, "an object");
});

describe("applied to a promise fulfilled and then chained off of", function () {
    function testNonFunction(nonFunction, stringRepresentation) {
        specify("`onRejected` is " + stringRepresentation, function (done) {
            resolved(dummy).then(undefined, function () { }).then(function () {
                done();
            }, nonFunction);
        });
    }
    // 当 onFulfilled 是非函数的情况,不会影响then之后的链式调用
    testNonFunction(undefined, "`undefined`");
    testNonFunction(null, "`null`");
    testNonFunction(false, "`false`");
    testNonFunction(5, "`5`");
    testNonFunction({}, "an object");
});

不要看着代码长就怕,我们简化一下,得到下面四种情况:

javascript 复制代码
// onFulfilled 是非函数
// 不影响onRejected的执行
1. promise.rejected().then(undefined, function () { })
// 可以直接将当前promise的已完成状态传递给链式调用的下一个promise
2. promise.resolve().then(undefined, function () { }).then(function() {}, undefined) 

// onRejected 是非函数
// 不影响当前promise onFulfilled 的执行
3. promise.resolve().then(function () { }, undefined) 
// 可以直接将当前promise的拒绝状态传递给链式调用的下一个promise
4. promise.rejected().then(function () { }, undefined).then(undefined, function() {}) 

这里的2和4用到规则2.7的知识,我们还没讲到,这里可以简单了解一下,后续会展开,即 then方法必须返回个全新的promise ,所以才能进行链式的调用,所以简而言之,「虽然忽略了当前的非函数方法,但是依然会把当前promise的状态正确的传递给下一个环节

这么一看图片是不是清晰一点,但是这里也有一个问题,传递给后续 promisevaluereason 是当前 promise 的吗,还是 onFulfilledonRejected ?,话不多说,上代码吧:

javascript 复制代码
Promise.resolve(1).then(undefined, () => {}).then((value) => {
    console.log(value)
}, undefined);

Promise.reject(2).then(() => {}, undefine).then(undefined, (value) => {
    console.log(value)
});

// 1
// 2

看起来是真的忽略了非函数的情况,所以这个时候我们就可以这么来写了:

javascript 复制代码
then(onFulfilled, onRejected) {
    let newPromise = new myPromise((reslove, reject) => {
        if (this.#promiseState === FULFILLED) {
            if (typeof onFulfilled !== 'function') {
                // 把当前的value传递新的promise
                reslove(this.#promiseState)
            } else {
                // todo 1
            }
        } else if (this.#promiseState === REJECTED) {
            if (typeof onRejected !== 'function') {
                // 把当前的season传递新的promise
                reject(this.#promiseState)
            } else {
                // todo 2
            }   
        } else {
            // todo 3   
        }
    });
    // 这是2.7.2 的规则,这里先简单实现
    return newPromise;
}

到这里应该能满足2.2.1 规范了,成功的一大步!!!坚持住~

1.4.2 规范「2.2.2」

如果 onFulfilled 是一个函数:

它一定会在当前promise状态切换为 fulfilled 时被调用,同时promisevalue作为它的第一个参数。

它一定不能在当前promise的状态切换为 fulfilled 之前被调用。

它只能被调用一次

这里需要注意两点:

  1. onFulfilled 要在promise被切换为fulfilled被调用,这就要分两种情况
    1.1 第一种情况:调用 then 的时候, promise 的状态正好是 fulfilled

    javascript 复制代码
        let p = new MyPromise((resolve, reject) => {
            resolve('success');
        })
        p.then(res => {
            console.log(res);
        })
    
        // success
        
        这种情况就比较好满足了

    1.2 第二种情况:调用 then 的时候, promise 的状态正好是 pending

    javascript 复制代码
        let p = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                resolve('success');
            }, 10000);
        })
        p.then(res => {
            console.log(res);
        })

    这也对应我们上面的todo 3 待补充的逻辑

  2. 它只能被调用一次 我们这里没有处理多次调用的问题,需要考虑一下怎么处理

接下来针对上面的几种情况我们进行code:

1.4.2.1 promise 的状态是 fulfilled

只写重要的代码片段哈

javascript 复制代码
then(onFulfilled, onRejected) {
    let newMyPromise =  new MyPromise((resolve,reject) => {
        if (this.#promiseState === FULFILLED) {
            if (typeof onFulfilled !== 'function') {
                // 把当前的value传递新的promise
                resolve(this.#promiseState);
            } else {
                // 将当前的promise value作为第一个参数调用 onFulfilled
                onFulfilled(this.#promiseState);
                // 这个规范 到这里还没提到 是否调用resolve,我们先不处理
                // resolve
            }
        }
    });
    return newMyPromise;
}
1.4.2.1 promise 的状态是 pending

这个是比较难处理的,这个也正是promise的精髓所在,我们思考的大致逻辑是这样的:

1.当调用then的时候,如果promisefulfilled,那就直接调用 thenonFulfilled 方法

2.当调用then的时候,如果promisepending,我们就需要把 onFulfilled 缓存 起来,在将来的某个时候,promise 状态变成 onFulfilled 的时候调用

所以这个关键词就俩:缓存调用

还记得在1.1 promise属性的介绍里我增加了一个microTaskQuene 的属性,养兵千日,用兵一时,该它出场啦(其实到这个规范这里,我们设置一个对象或者变量就可以缓存onFulfilled 方法,之所以使用数组来存储的原因是,后面还有2.2.6规范要用,也显得我这个设计有远见,哈哈),上代码:

javascript 复制代码
then(onFulfilled, onRejected) {
   let newMyPromise =  new MyPromise((resolve,reject) => {
       if (this.#promiseState === PENDING) {
           this.#microTaskQueue.push({
               onFulfilled: () => {
                   try{
                       onFulfilled(this.#promiseResult)
                   }catch(e){
                       reject(e);
                   }
               },
               onRejected: () => {
                   setTimeout(() => {
                       try{
                           onRejected(this.#promiseResult)
                       } catch (e){
                           reject(e);
                       }
                   }, 0)
               }
           })
       }
   });
   return newMyPromise;
}

这样就可以同时将onFulfilledonRejected缓存起来啦,那什么时候调用呢,上面说的很清楚,即未来某个时刻promise 状态变成 onFulfilled 的时候调用 它,所以要修改我们的constructor代码如下:

kotlin 复制代码
constructor(executor) {
    let myResolve = (value) => {
        if (this.#promiseState === PENDING) {
            this.#promiseState = FULFILLED;
            this.#promiseResult = value;
            // 增加下面的逻辑
            this.#microTaskQueue.forEach(item => {
                item.onFulfilled(value);
            });
        }
    }
    let myReject = (reason) => {
        if (this.#promiseState === PENDING) {
            this.#promiseState = REJECTED;
            this.#promiseResult = reason;
            // 增加下面的逻辑
            this.#microTaskQueue.forEach(item => {
                item.onRejected(reason);
            });
        }
    }
    try {
        executor(myResolve, myReject);
    } catch (e) {
        this.#promiseState = REJECTED;
        this.#promiseResult = e;
    }
}

正好补充了我们上面的遗留的todo,那现在只剩下一个了问题啦: 它只能被调用一次 由于resolve和reject只会执行一次,所以看起来已经满足了只能调用一次的特性

到这里应该基本完成了2.2.2的规范

小结

到这里的时候我们的代码如下:

csharp 复制代码
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
    #promiseState = PENDING;
    #promiseResult = undefined;
    #microTaskQueue = [];

    constructor(executor) {
        let myResolve = (value) => {
            if (this.#promiseState === PENDING) {
                this.#promiseState = FULFILLED;
                this.#promiseResult = value;
                // 增加下面的逻辑
                this.#microTaskQueue.forEach(item => {
                    item.onFulfilled(value);
                });
            }
        }
        let myReject = (reason) => {
            if (this.#promiseState === PENDING) {
                this.#promiseState = REJECTED;
                this.#promiseResult = reason;
                // 增加下面的逻辑
                this.#microTaskQueue.forEach(item => {
                    item.onRejected(reason);
                });
            }
        }
        try {
            executor(myResolve, myReject);
        } catch (e) {
            this.#promiseState = REJECTED;
            this.#promiseResult = e;
        }
    }

    then(onFulfilled, onRejected){
        let newMyPromise = new MyPromise((resolve,reject) => {
            if (this.#promiseState === FULFILLED) {
                if (typeof onFulfilled !== 'function') {
                    // 把当前的value传递新的promise
                    resolve(this.#promiseResult);
                } else {
                    // 将当前的promise value作为第一个参数调用 onFulfilled
                    onFulfilled(this.#promiseResult);
                    // 这个规范 到这里还没提到 是否调用resolve,我们先不处理
                    // resolve
                }
            } if (this.#promiseState === REJECTED) {
                if (typeof onFulfilled !== 'function') {
                    // 把当前的value传递新的promise
                    reject(this.#promiseResult);
                } else {
                    // 将当前的promise value作为第一个参数调用 onFulfilled
                    onRejected(this.#promiseResult);
                    // 这个规范 到这里还没提到 是否调用resolve,我们先不处理
                    // resolve
                }
            } else if (this.#promiseState === PENDING) {
                this.#microTaskQueue.push({
                    onFulfilled: () => {
                        try{
                            onFulfilled(this.#promiseResult)
                        }catch(e){
                            reject(e);
                        }
                    },
                    onRejected: () => {
                        setTimeout(() => {
                            try{
                                onRejected(this.#promiseResult)
                            } catch (e){
                                reject(e);
                            }
                        }, 0)
                    }
                })
            }
        });
        return newMyPromise;
    }
    catch(){}
    finally(){}

    // 下面是在Promise类上面的方法
    static resolve(value){}
    static reject(reason){}
    static race([]){}
    static all([]){}
    static allSettled([]){}
    static try(f){}

}

1.4.3 规范「2.2.3」

如果 onRejected 是一个函数
2.2.3.1 它一定会在当前promise状态切换为 rejected 时被调用,同时promisereason作为它的第一个参数。
2.2.3.2 它一定不能在当前promise的状态切换为 rejected 之前被调用。
2.2.3.3 它只能被调用一次

这里的实现和1.4.2一样,在小结中已经体现,这里就不多赘述啦

1.4.4 规范「2.2.4」

onFulfilledonRejected 只有在执行上下文堆栈仅包含平台代码 时才可被调用。[3.1]

各位是不是和我一样,开始的看着是不是有点懵逼,看规范的注释解释:

这里的"平台代码"是指引擎、环境和promise实现代码。在实践中,这一要求确保onFulfilledonRejected在调用事件循环之后异步执行,并使用新的堆栈。这可以用"宏任务"机制(如setTimeoutsetImmediate)实现,也可以用"微任务"机制,如MutationObserverprocess.nextTick实现。

推测像是执行onFulfilledonRejected要使用异步方式进行调用,而我们上面是采用的同步方式进行的,当然了,只是推测,我们看一下这个规范的测试case咋写的,这里比较相似,只挑两个代表看一下:

javascript 复制代码
describe("`then` returns before the promise becomes fulfilled or rejected", function () {
    testFulfilled(dummy, function (promise, done) {
        var thenHasReturned = false;

        promise.then(function onFulfilled() {
            // 这里期望即使先调用了then方法,但是thenHasReturned仍被下面的代码改成了true,
            // 也说明onFulfilled方法实际是在 thenHasReturned = true; 后面执行的
            assert.strictEqual(thenHasReturned, true);
            done();
        });

        thenHasReturned = true;
    });
    testRejected(dummy, function (promise, done) {
        var thenHasReturned = false;
        // 这里期望即使先调用了then方法,但是thenHasReturned仍被下面的代码改成了true,
        // 也说明onRejected方法实际是在 thenHasReturned = true; 后面执行的
        promise.then(null, function onRejected() {
            assert.strictEqual(thenHasReturned, true);
            done();
        });

        thenHasReturned = true;
    });
});

这里如果展开说的话,就涉及到js的事件循环机制 ,感兴趣的同学可以自己先搜搜看看,我之后会有单独的文章来讲解,这里呢,大家可以简单理解为,onFulfilledonRejected 的执行要使用异步执行,实现的方式可以使用用"宏任务"机制(如setTimeoutsetImmediate)实现,也可以用"微任务"机制,如MutationObserverprocess.nextTick实现,这里我们可以使用setTimeout来实现异步效果,代码如下:

scss 复制代码
then(onFulfilled, onRejected){
    let newMyPromise = new MyPromise((resolve,reject) => {
        if (this.#promiseState === FULFILLED) {
            setTimeout(() => {
                if (typeof onFulfilled !== 'function') {
                    resolve(this.#promiseResult);
                } else {
                    try{
                        onFulfilled(this.#promiseResult);
                    }catch(e){
                        reject(e);
                    }
                }
            }, 0)
        } if (this.#promiseState === REJECTED) {
            setTimeout(() => {
                if (typeof onRejected !== 'function') {
                    reject(this.#promiseResult);
                } else {
                    try{
                        onRejected(this.#promiseResult);
                    }catch(e){
                        reject(e);
                    }
                }
            }, 0)
        } else if (this.#promiseState === PENDING) {
            this.#microTaskQueue.push({
                onFulfilled: () => {
                    setTimeout(() => {
                        if (typeof onFulfilled !== 'function') {
                            resolve(this.#promiseResult);
                        } else {
                            try{
                                onFulfilled(this.#promiseResult);
                            }catch(e){
                                reject(e);
                            }
                        }
                    }, 0)
                },
                onRejected: () => {
                    setTimeout(() => {
                        if (typeof onRejected !== 'function') {
                            reject(this.#promiseResult);
                        } else {
                            try{
                                onRejected(this.#promiseResult);
                            }catch(e){
                                reject(e);
                            }
                        }
                    }, 0)
                }
            })
        }
    });
    return newMyPromise;
}

这样就满足了异步调用的效果啦,继续继续!

1.4.5 规范「2.2.5」

onFulfilledonRejected必须作为函数进行调用(即没有this值) 也就是说,在严格模式下,这在它们内部是 undefined ;在非严格模式下,它将是全局对象。

效果如下:

javascript 复制代码
Promise.resolve().then(function() {"use strict";console.log(this)})
// undefined

Promise.resolve().then(function() {console.log(this)})

// 浏览器下是 window
// node环境下是 global

这里主要说明的是一种场景,即onFulfilledonRejected是作为对象的方法进行调用时的情况,如下面的代码:

javascript 复制代码
class testClass {
    constructor() {
      this.value = 1;
    }
    
    test() {
      console.log(this.value);
    }
}
  
const obj = new testClass();

MyPromise.resolve().then(obj.test)
// 这里会抛出异常 提示 TypeError: Cannot read properties of undefined (reading 'value')

这里从测试case上并没有针对这个场景的测试,唯一做的就是当用户传递的不正确的时候,抛出这个异常,所以我们能做也是在onFulfilledonRejected调用时做好try catch,上一小节我们的代码已经处理,哈哈,明智吧

1.4.5 规范「2.2.6」

then 可能会在同一个promise上多次进行调用

如果promise的状态是已完成,所有的相应的 onFulfilled 回调都必须按照调用的顺序执行。

如果promise的状态是拒绝状态,所有的相应的 onRejected 回调都必须按照调用的顺序执行。

说人话就是,then可以被调用多次,而最终的回调都会按照调用的顺序进行。

这里问一个愚蠢的问题,我第一看这个规范的时候,想当然的以为是这样的:

scss 复制代码
Promise.resolve().then().then().then()...then()

不知道有没有和我一样的,实际上是这样的:

javascript 复制代码
let p = Promise.resolve();
p.then(() => console.log(1));
p.then(() => console.log(2));
p.then(() => console.log(3));
p.then(() => console.log(4));

所以这里终于用到了上面的 #microTaskQuene属性了,promise 如果是 fulfilled 和 pending 状态下的promise 直接按顺序调用,顺序执行即可,只是pending状态下,就需要按照顺序缓存起来:

scss 复制代码
this.#microTaskQueue.push({
    onFulfilled: () => {
        try{
            onFulfilled(this.#promiseResult)
        }catch(e){
            reject(e);
        }
    },
    onRejected: () => {
        setTimeout(() => {
            try{
                onRejected(this.#promiseResult)
            } catch (e){
                reject(e);
            }
        }, 0)
    }
})

在再需要的时候,按顺序进行执行:

ini 复制代码
this.#microTaskQueue.forEach(item => {
    item.onFulfilled(value);
});

上面的1.4.2的小节已经写过啦,就不过多赘述了

1.4.5 规范「2.2.7」

then 必须返回一个promise[3.3],如下:

promise2 = promise1.then(onFulfilled, onRejected);

1. 如果 onFulfilledonRejected 返回值一个值 x,则运行Promise解析过程 [[Resolve]](promise2, x)。

2.如果 onFulfilled 或者 onRejected 抛出异常 e ,则 promise2 必须以e作为reasonpromise2置为rejected

3.如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须和promise1一样的value将状态置为 fulfilled

4.如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回和promise1相同的reason

还记得我们上面代码,怕你们忘了,我再贴一遍:

scss 复制代码
setTimeout(() => {
    try{
        // 这里我们 只是 调用onFulfilled ,并没有处理返回promise的状态
        onFulfilled(this.#promiseState);
    }catch(e){
        reject(e);
    }
}, 0)

而对于promise的链式调用来说,我们需要将当前promise的状态传递给返回的promise中,所以我们按照上面的4条规则,可以这样来写代码:

javascript 复制代码
// 1. 如果 `onFulfilled` 或 `onRejected` 返回值一个值 `x`,则运行`Promise`解析过程 [[Resolve]](promise2, x)。
    try {
        let x = onFulfilled(this.#promiseResult)
        // or
        let x = onRejected(this.#promiseResult)
    } catch(e) {
        // 2.如果 `onFulfilled` 或者 `onRejected` 抛出异常 `e` ,则 `promise2` 必须以`e`作为`reason`将`promise2`置为`rejected`
        reject(e);
    }
    // 调用[[Resolve]](promise2, x)
    // 我们命名resolvePromise
    resolvePromise(newPromise, x);

这里注意两点:

  1. 上面的3和4我们在处理2.2.1 规则的时候已经通过分析测试case解决了(此处应该有掌声)
  2. resolvePromise 具体怎么实现,我们这里先不用关心,会单独抽取一个方法实现,而且透露一下,上面只传递两个参数是不够的,后面再讨论

到此为止,关于then方法的实现到此结束,看看我们的成果吧;

javascript 复制代码
then(onFulfilled, onRejected){
    let newMyPromise = new MyPromise((resolve,reject) => {
        if (this.#promiseState === FULFILLED) {
            setTimeout(() => {
                if (typeof onFulfilled !== 'function') {
                    resolve(this.#promiseResult);
                } else {
                    try{
                        let x = onFulfilled(this.#promiseResult);
                        resolvePromise(newMyPromise, x);
                    }catch(e){
                        reject(e);
                    }
                }
            }, 0)
        } if (this.#promiseState === REJECTED) {
            setTimeout(() => {
                if (typeof onRejected !== 'function') {
                    resolve(this.#promiseResult);
                } else {
                    try{
                        let x = onRejected(this.#promiseResult);
                        resolvePromise(newMyPromise, x);
                    }catch(e){
                        reject(e);
                    }
                }
            }, 0)
        } else if (this.#promiseState === PENDING) {
            this.#microTaskQueue.push({
                onFulfilled: () => {
                    setTimeout(() => {
                        if (typeof onFulfilled !== 'function') {
                            resolve(this.#promiseResult);
                        } else {
                            try{
                                let x = onFulfilled(this.#promiseResult);
                                resolvePromise(newMyPromise, x);
                            }catch(e){
                                reject(e);
                            }
                        }
                    }, 0)
                },
                onRejected: () => {
                    setTimeout(() => {
                        if (typeof onRejected !== 'function') {
                            resolve(this.#promiseResult);
                        } else {
                            try{
                                let x = onRejected(this.#promiseResult);
                                resolvePromise(newMyPromise, x);
                            }catch(e){
                                reject(e);
                            }
                        }
                    }, 0)
                }
            })
        }
    });
    return newMyPromise;
}

哈哈,你可能也发现代码比较重复了,但是我们先不着急优化哈,最后有的是空间!

1.4.6 resolvePromise方法

在规范中有一半的篇幅是关于这个方法的实现,可见其分量,而且它其实也是在then方法中进行调用,所以我也就把它归到then方法中啦。

2.3.1 如果 promisex 是同一个对象的引用, 则直接将TypeError作为reason拒绝promise.
2.3.2 如果x是一个promise,则取决于它的状态 [3.4]:
2.3.2.1 如果x的状态是 pending, promise 必须在 x 被执行和被拒绝之前保持pending状态。
2.3.2.2 如果x的状态是 fulfilled, 则使用相同的value执行promise.
2.3.2.3 如果x的状态是rejected,则使用相同的reason拒绝promise.
2.3.3 另外,如果x是一个对象或者函数时:
2.3.3.1then方法赋值为x.then. [3.5]
2.3.3.2 如果读取属性x.then导致抛出异常e,则以e作为reason拒绝promise
2.3.3.3 如果then是一个函数,则将x作为它的this进行调用(可以理解为:x.then().bind(x)),同时它的第一个参数 是resolvePromise, 第二个参数是 rejectPromise,关于这两个参数:
2.3.3.3.1 如果使用value y调用resolvePromise,则运行[[Resolve]](promise, y).
2.3.3.3.2 如果使用reason r调用 rejectPromise ,则使用r作为reason拒绝promise
2.3.3.3.3 如果 resolvePromiserejectPromise 都被调用,或者使用同一参数调用了多次,则优先采用第一次调用并且忽略其他所有后续的调用.
2.3.3.3.4 如果调用 then 方法出现了一个异常 e
2.3.3.3.4.1 如果 resolvePromiserejectPromise 已经被调用,则忽略这个异常.
2.3.3.3.4.2 否则 将上述的e作为 reason 拒绝promise.
2.3.3.4 如果 then 不是一个函数,则直接使用x作为value来实现promise.
2.3.4 如果x不是一个对象或者函数,则直接使用x作为value来实现promise.

篇幅不短,但是不要怕哈,像上面一样我们一条一条进行分析:

首先是函数的参数,规则中写的是:

lua 复制代码
[[Resolve]](promise,x)

其中promise 是then方法需要返回的 newMyPromisexonFulfilledonRejected的返回值,但是看着规则,我们需要在后续的执行中来更新 newMyPromise 的状态,显然两个参数是不够的,需要将resolvereject传入方法中:

javascript 复制代码
function resolvePromise(newMyPromise, x, resolve, reject) {
    // 2.3.1 如果 `promise` 和 `x` 是同一个对象的引用, 则直接将`TypeError`作为`reason`拒绝`promise`.
    if (newMyPromise === x) {
        reject(new TypeError('Chaining cycle'));
    }
    // 2.3.2 如果 `x` 是thenable, 使用它处理`promise`.
    if (x instanceof MyPromise) {
        x.then(resolve, reject);
    } else if (['[object Object]', '[object Function]'].includes(Object.prototype.toString.call(x))) {
        // 2.3.3 另外,如果x是一个对象或者函数时:
        try {
            // 2.3.3.1 将then方法赋值为x.then
            let then = x.then;
            if (typeof then === 'function') {
                let state = true;
                try {
                    state = false;
                    // 2.3.3.3 如果then是一个函数,则将x作为它的this进行调
                    then.call(x, (y) => {
                        // 2.3.3.3.1 如果使用value y调用resolvePromise,则运行[[Resolve]](promise, y).
                        if (state) return;
                        state = true;
                        resolvePromise(newMyPromise, y, resolve, reject);
                    }, (r) => {
                        if (state) return;
                        state = true;
                        // 2.3.3.3.2 如果使用reason r调用 rejectPromise ,则使用r作为reason拒绝promise。
                        reject(r);
                    });
                } catch (e) {
                    if (!state) {
                        // 2.3.3.3.4.2 否则 将上述的e作为 reason 拒绝promise.。
                        reject(e);
                    }
                }
            } else {
                // 2.3.3.4 如果 then 不是一个函数,则直接使用x作为value来实现promise.
                resolve(x);
            }
        } catch(e) {
            // 2.3.3.2 如果读取属性x.then导致抛出异常e,则以e作为reason拒绝promise。
            reject(e);
        }
    } else {
        // 2.3.4 如果x不是一个对象或者函数,则直接使用x作为value来实现promise
        resolve(x);
    }
}

终于,终于把promise核心搞完了,剩下的就是小菜啦

1.5 resolve 方法

下面的方法没有规范可以参考啦,我们只能根据用法来实现了

Promise.resolve()方法的主要作用是将现有对象转换为promise对象:

  1. 参数是一个 Promise 实例 不做任何修改、原封不动地返回这个实例。
  2. 参数是一个thenable对象 会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法。
  3. 参数不是具有then()方法的对象,或根本就不是对象 :返回一个新的 Promise 对象,状态为resolved
  4. 不带有任何参数 : 允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。

所以和上面一样,我们来实现如下:

javascript 复制代码
static resolve(value) {
     // 参数是一个 Promise 实例
    if (value instanceof MyPromise) {
        return value;
    }
    if (typeof value === 'object' && typeof value.then === 'function') {
        // 参数是一个thenable对象
        // 这里看着阮一峰的翻译感觉有点不是很明白
        // 但是很像promise/A+规范中 then方法的一句话
        // 关于如果x是thenable,这种情况下,可以把x看作是一个promise,此时试图使promise采用x的状态。否则,它将用x作为value来完成promise。

        let promise = new MyPromise((resolve, reject) => {
            value.then((y) => {
                // 2.3.3.3.1 如果使用value y调用resolvePromise,则运行[[Resolve]](promise, y).
                resolvePromise(promise, y, resolve, reject);
            }, (r) => {
                // 2.3.3.3.2 如果使用reason r调用 rejectPromise ,则使用r作为reason拒绝promise。
                reject(r);
            });
        });
    } else {
        // 参数是一个普通值或者为空
        return new MyPromise((resolve, reject) => {
            resolve();
        });
    }
}

这里的第二条比较难以处理和理解,可以看一个例子:

typescript 复制代码
let obj = {
    then: (resolve, reject) => {
        resolve(2)
    }
}

Promise.resolve(obj).then(res => {
    console.log('res',  res);
}).catch(e => {
    console.log('catch',  e);
})

// res 2

此时看着没问题,但是如果我更新一下:

typescript 复制代码
let obj = {
    then: (resolve, reject) => {
        reject(2)
    }
}

Promise.resolve(obj).then(res => {
    console.log('res',  res);
}).catch(e => {
    console.log('catch',  e);
})

// catch 2

所以resolve 的返回值promise不一定是fulfilled状态,同时也引出一个问题,Promise.resolve和promise(resolve => resolve()) 是否等价,看一个例子:

javascript 复制代码
let p = new Promise((resolved,  rejected) => {
    rejected(1);
});

let p1 = new Promise(resolved => resolved(p));

let p2 = Promise.resolve(p);

console.log(p, p1, p2, p1 === p, p2 === p);

忽略报错,我们可以看到打印

Promise { 1 }

Promise { }

Promise { 1 }

false

true

所以Promise的resolve方法和promise(resolve => resolve())是不等价的,也所以看到有的文章实现如下:

javascript 复制代码
static resolve(value) {
     // 参数是一个 Promise 实例
    if (value instanceof MyPromise) {
        return value;
    }
    return new MyPromise((resolve, reject) => {
        resolve();
    });
} 

这里正好提到了我们在constructor中遗留的关于resolve的问题,有没有可能resolve就是这么写的,而是我们构造方法写的有问题呢?之所以放在这里讲的原因是我们在then方法中已经有了关于resolvePromise方法的实现,其中有关于value类型对于处理逻辑的影响,所以我们参考改造一下我们的构造方法:

javascript 复制代码
constructor(executor) {
    let myResolve = (value) => {
        if (this.#promiseState === PENDING) {
            if (value === this) {
                myReject(new TypeError('Chaining cycle'));
            }
            // 2.3.3如果x是一个promise,则取决于它的状态.
            if (value instanceof MyPromise) {
                value.then(myResolve, myReject);
            } else if (['[object Object]', '[object Function]'].includes(Object.prototype.toString.call(value))) {
                // 2.3.3 另外,如果x是一个对象或者函数时:
                try {
                    // 2.3.3.1 将then方法赋值为x.then
                    let then = value.then;
                    if (typeof then === 'function') {
                        let state = true;
                        try {
                            state = false;
                            // 2.3.3.3 如果then是一个函数,则将x作为它的this进行调
                            then.call(value, (y) => {
                                // 2.3.3.3.1 如果使用value y调用resolvePromise,则运行[[Resolve]](promise, y).
                                if (state) return;
                                state = true;
                                resolvePromise(this, y, myResolve, myReject);
                            }, (r) => {
                                if (state) return;
                                state = true;
                                // 2.3.3.3.2 如果使用reason r调用 rejectPromise ,则使用r作为reason拒绝promise。
                                myReject(r);
                            });
                        } catch (e) {
                            if (!state) {
                                // 2.3.3.3.4.2 否则 将上述的e作为 reason 拒绝promise.。
                                myReject(e);
                            }
                        }
                    } else {
                        // 2.3.3.4 如果 then 不是一个函数,则直接使用x作为value来实现promise.
                        this.#promiseState = FULFILLED;
                        this.#promiseResult = value;
                        // 增加下面的逻辑
                        this.#microTaskQueue.forEach(item => {
                            item.onFulfilled(value);
                        });
                    }
                } catch(e) {
                    // 2.3.3.2 如果读取属性x.then导致抛出异常e,则以e作为reason拒绝promise。
                    myReject(e);
                }
            } else {
                // 2.3.4 如果x不是一个对象或者函数,则直接使用x作为value来实现promise
                this.#promiseState = FULFILLED;
                this.#promiseResult = value;
                // 增加下面的逻辑
                this.#microTaskQueue.forEach(item => {
                    item.onFulfilled(value);
                });
            }
        }
    }
    let myReject = (reason) => {
        if (this.#promiseState === PENDING) {
            this.#promiseState = REJECTED;
            this.#promiseResult = reason;
            // 增加下面的逻辑
            this.#microTaskQueue.forEach(item => {
                item.onRejected(reason);
            });
        }
    }
    try {
        executor(myResolve, myReject);
    } catch (e) {
        this.#promiseState = REJECTED;
        this.#promiseResult = e;
    }
}

这么处理的话,看起来都OK了

1.6 reject 方法

reject的实现和resolve不同,我没写之前,我以为是和resolve一样的,哈哈,没想到,你传递啥都是直接作为reason给你抛出来,即使是上面的例子:

javascript 复制代码
let obj = {
    then: (resolve, reject) => {
        resolve(2)
    }
}

Promise.resolve(obj).then(res => {
    console.log('res',  res);
}).catch(e => {
    console.log('catch',  e);
})

// catch { then: [Function: then] }

// promise 也不例外,果然拒绝比接受容易多了
Promise.reject(new Promise((resolve, reject) => {})).then(res => {
    console.log('res',  res);
}).catch(e => {
    console.log('catch',  e);
})

// catch Promise { <pending> }

直接把obj最为reason扔出来了,所以代码如下:

javascript 复制代码
static reject(reason) {
    return new MyPromise((resolve, reject) => {
        reject(reason);
    });
}

1.7 catch 方法

Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

这一句话就够了:

kotlin 复制代码
catch(func) {
    return this.then(null, func);
}

1.8 finally 方法

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

这里我又犯了一个错误,我以为finally会在最后执行,不论then方法里面会执行多久,但是我又错了,看下面代码

javascript 复制代码
new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
    }, 1000)
}).then((res) => {
    console.log(2.2)
    setTimeout(() => {
        console.log(2)
    }, 1000)
}).then((res) => {
    console.log(3.3)
    setTimeout(() => {
        console.log(3)
    }, 1000)
}).then((res) => {
    console.log(4.4)
    setTimeout(() => {
        console.log(4)
    }, 1000)
}).finally(e => {
    console.log(e)
})

// 打印
// 2.2
// 3.3
// 4.4
// undefined
// 2
// 3
// 4

// 调整一下finally的顺序

new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
    }, 1000)
}).then((res) => {
    console.log(2.2)
    setTimeout(() => {
        console.log(2)
    }, 1000)
}).finally(e => {
    console.log(e)
}).then((res) => {
    console.log(3.3)
    setTimeout(() => {
        console.log(3)
    }, 1000)
}).then((res) => {
    console.log(4.4)
    setTimeout(() => {
        console.log(4)
    }, 1000)
})

// 打印
// 2.2
// undefined
// 3.3
// 4.4
// 2
// 3
// 4

所以可以得到结论如下:

  1. finally是在promise状态更新之后执行(并不是在所有thencatch之后执行),单纯只是表示状态变化一定会执行
  2. 执行的时机和声明的顺序有关,按照声明的顺序进行执行
  3. 不会影响promise往后传递的值

所以代码如下:

javascript 复制代码
finally(callback) {
    return new MyPromise((resolve, reject) => {
        if (this.#promiseState !== PENDING) {
            setTimeout(() => {
                callback();
                if (this.#promiseState === FULFILLED) {
                    resolve(this.#promiseState);
                } else {
                    reject(this.#promiseState);
                }
            }, 0)
        } else {
            this.#microTaskQueue.push({
                onFulfilled: () => {
                    setTimeout(() => {
                        callback();
                        resolve(this.#promiseState);
                    }, 0)
                },
                onRejected: () => {
                    setTimeout(() => {
                        callback();
                        reject(this.#promiseState);
                    }, 0)
                }
            })
        }
    })
}

写到这是不是回过来一点味,这个逻辑不就是要,promise只要状态改变的时候,执行finally的回调,同时返回一个promise,借一下阮一峰的文档中的描述:

所以可以优化一下代码:

javascript 复制代码
function resolvePromise(newMyPromise, x, resolve, reject) {
    // 2.3.1 如果 `promise` 和 `x` 是同一个对象的引用, 则直接将`TypeError`作为`reason`拒绝`promise`.
    if (newMyPromise === x) {
        reject(new TypeError('Chaining cycle'));
    }
    // 2.3.3如果x是一个promise,则取决于它的状态.
    if (x instanceof MyPromise) {
        x.then(y => resolvePromise(newMyPromise, y, resolve, reject), reject);
    } else if (['[object Object]', '[object Function]'].includes(Object.prototype.toString.call(x))) {
        // 2.3.3 另外,如果x是一个对象或者函数时:
        try {
            // 2.3.3.1 将then方法赋值为x.then
            let then = x.then;
            if (typeof then === 'function') {
                let state = true;
                try {
                    state = false;
                    // 2.3.3.3 如果then是一个函数,则将x作为它的this进行调
                    then.call(x, (y) => {
                        // 2.3.3.3.1 如果使用value y调用resolvePromise,则运行[[Resolve]](promise, y).
                        if (state) return;
                        state = true;
                        resolvePromise(newMyPromise, y, resolve, reject);
                    }, (r) => {
                        if (state) return;
                        state = true;
                        // 2.3.3.3.2 如果使用reason r调用 rejectPromise ,则使用r作为reason拒绝promise。
                        reject(r);
                    });
                } catch (e) {
                    if (!state) {
                        // 2.3.3.3.4.2 否则 将上述的e作为 reason 拒绝promise.。
                        reject(e);
                    }
                }
            } else {
                // 2.3.3.4 如果 then 不是一个函数,则直接使用x作为value来实现promise.
                resolve(x);
            }
        } catch(e) {
            // 2.3.3.2 如果读取属性x.then导致抛出异常e,则以e作为reason拒绝promise。
            reject(e);
        }
    } else {
        // 2.3.4 如果x不是一个对象或者函数,则直接使用x作为value来实现promise
        resolve(x);
    }
}

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

class MyPromise {
    #promiseState = PENDING;
    #promiseResult = '';
    #microTaskQueue = [];

    constructor(executor) {
        let myResolve = (value) => {
            if (this.#promiseState === PENDING) {
                if (value === this) {
                    myReject(new TypeError('Chaining cycle'));
                }
                // 2.3.3如果x是一个promise,则取决于它的状态.
                if (value instanceof MyPromise) {
                    value.then(myResolve, myReject);
                } else if (['[object Object]', '[object Function]'].includes(Object.prototype.toString.call(value))) {
                    // 2.3.3 另外,如果x是一个对象或者函数时:
                    try {
                        // 2.3.3.1 将then方法赋值为x.then
                        let then = value.then;
                        if (typeof then === 'function') {
                            let state = true;
                            try {
                                state = false;
                                // 2.3.3.3 如果then是一个函数,则将x作为它的this进行调
                                then.call(value, (y) => {
                                    // 2.3.3.3.1 如果使用value y调用resolvePromise,则运行[[Resolve]](promise, y).
                                    if (state) return;
                                    state = true;
                                    resolvePromise(this, y, myResolve, myReject);
                                }, (r) => {
                                    if (state) return;
                                    state = true;
                                    // 2.3.3.3.2 如果使用reason r调用 rejectPromise ,则使用r作为reason拒绝promise。
                                    myReject(r);
                                });
                            } catch (e) {
                                if (!state) {
                                    // 2.3.3.3.4.2 否则 将上述的e作为 reason 拒绝promise.。
                                    myReject(e);
                                }
                            }
                        } else {
                            // 2.3.3.4 如果 then 不是一个函数,则直接使用x作为value来实现promise.
                            this.#promiseState = FULFILLED;
                            this.#promiseResult = value;
                            // 增加下面的逻辑
                            this.#microTaskQueue.forEach(item => {
                                item.onFulfilled(value);
                            });
                        }
                    } catch(e) {
                        // 2.3.3.2 如果读取属性x.then导致抛出异常e,则以e作为reason拒绝promise。
                        myReject(e);
                    }
                } else {
                    // 2.3.4 如果x不是一个对象或者函数,则直接使用x作为value来实现promise
                    this.#promiseState = FULFILLED;
                    this.#promiseResult = value;
                    // 增加下面的逻辑
                    this.#microTaskQueue.forEach(item => {
                        item.onFulfilled(value);
                    });
                }
            }
        }
        let myReject = (reason) => {
            if (this.#promiseState === PENDING) {
                this.#promiseState = REJECTED;
                this.#promiseResult = reason;
                // 增加下面的逻辑
                this.#microTaskQueue.forEach(item => {
                    item.onRejected(reason);
                });
            }
        }
        try {
            executor(myResolve, myReject);
        } catch (e) {
            this.#promiseState = REJECTED;
            this.#promiseResult = e;
        }
    }

    static resolve(value) {
         // 参数是一个 Promise 实例
        if (value instanceof MyPromise) {
            return value;
        }
        // return new MyPromise((resolve, reject) => {
        //     resolve(value);
        // });
        return new MyPromise((resolve, reject) => {
            resolve(value);
        });
    }

    static reject(reason) {
        return new MyPromise((resolve, reject) => {
            reject(reason);
        });
    }

    then(onFulfilled, onRejected){
        let newMyPromise = new MyPromise((resolve,reject) => {
            if (this.#promiseState === FULFILLED) {
                setTimeout(() => {
                    if (typeof onFulfilled !== 'function') {
                        resolve(this.#promiseResult);
                    } else {
                        try{
                            let x = onFulfilled(this.#promiseResult);
                            resolvePromise(newMyPromise, x, resolve, reject);
                        }catch(e){
                            reject(e);
                        }
                    }
                }, 0)
            } if (this.#promiseState === REJECTED) {
                setTimeout(() => {
                    if (typeof onRejected !== 'function') {
                        reject(this.#promiseResult);
                    } else {
                        try{
                            let x = onRejected(this.#promiseResult);
                            resolvePromise(newMyPromise, x, resolve, reject);
                        }catch(e){
                            reject(e);
                        }
                    }
                }, 0)
            } else if (this.#promiseState === PENDING) {
                this.#microTaskQueue.push({
                    onFulfilled: () => {
                        setTimeout(() => {
                            if (typeof onFulfilled !== 'function') {
                                resolve(this.#promiseResult);
                            } else {
                                try{
                                    let x = onFulfilled(this.#promiseResult);
                                    resolvePromise(newMyPromise, x, resolve, reject);
                                }catch(e){
                                    reject(e);
                                }
                            }
                        }, 0)
                    },
                    onRejected: () => {
                        setTimeout(() => {
                            if (typeof onRejected !== 'function') {
                                reject(this.#promiseResult);
                            } else {
                                try{
                                    let x = onRejected(this.#promiseResult);
                                    resolvePromise(newMyPromise, x, resolve, reject);
                                }catch(e){
                                    reject(e);
                                }
                            }
                        }, 0)
                    }
                })
            }
        });
        return newMyPromise;
    }

    catch(func) {
        return this.then(null, func);
    }

    finally(callback) {
        return this.then(
            // 在 Promise 成功时执行回调,并保持原 Promise 的状态和值不变
            value => MyPromise.resolve(callback()).then(() => value),
            // 在 Promise 失败时执行回调,并保持原 Promise 的状态和值不变
            reason => MyPromise.resolve(callback()).then(() => { throw reason; })
        );
    }

    // 下面是在Promise类上面的方法
    static race([]){}
    static all([]){}
    static any([]){}
    static allSettled([]){}
    static try(f){}
}

接下来是骡子是马,跑跑测试case看一下吧

2. 测试

2.1 测试改造

我们按照官方的测试case文档,提示

  • resolved(value): creates a promise that is resolved with value.
  • rejected(reason): creates a promise that is already rejected with reason.
  • deferred(): creates an object consisting of { promise, resolve, reject }:
    • promise is a promise that is currently in the pending state.
    • resolve(value) resolves the promise with value.
    • reject(reason) moves the promise from the pending state to the rejected state, with rejection reason reason.

增加一下代码在最底部:

javascript 复制代码
MyPromise.resolved = MyPromise.resolve;
MyPromise.rejected = MyPromise.reject;

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

2.2 测试配置

npm install promises-aplus-tests -g

然后修复package.json的配置

javascript 复制代码
"scripts": {
    // 跟上你的promise文件名称,我这里是myPromise.js
  "test": "promises-aplus-tests myPromise.js --reporter spec"
  // 可以将执行结果保存到文件
  // "test": "promises-aplus-tests myPromise.js --reporter spec > test_report.txt"
}

执行测试:

pnpm test

or

npm run test

开始尽情测试吧

自信满满的run起来:

当然不是一次运行成功的,中间调问题也卡了很久,最终终于通过了

3. 优化

代码比较冗长,这里把重复逻辑抽取一下:

javascript 复制代码
function resolvePromise(newMyPromise, x, resolve, reject, myResolve) {
    // 2.3.1 如果 `promise` 和 `x` 是同一个对象的引用, 则直接将`TypeError`作为`reason`拒绝`promise`.
    if (newMyPromise === x) {
        reject(new TypeError('Chaining cycle'));
    }
    // 2.3.3如果x是一个promise,则取决于它的状态.
    if (x instanceof MyPromise) {
        x.then(resolve, reject);
    } else if (['[object Object]', '[object Function]'].includes(Object.prototype.toString.call(x))) {
        // 2.3.3 另外,如果x是一个对象或者函数时:
        try {
            // 2.3.3.1 将then方法赋值为x.then
            let then = x.then;
            if (typeof then === 'function') {
                let state = true;
                try {
                    state = false;
                    // 2.3.3.3 如果then是一个函数,则将x作为它的this进行调
                    then.call(x, (y) => {
                        // 2.3.3.3.1 如果使用value y调用resolvePromise,则运行[[Resolve]](promise, y).
                        if (state) return;
                        state = true;
                        resolvePromise(newMyPromise, y, resolve, reject);
                    }, (r) => {
                        if (state) return;
                        state = true;
                        // 2.3.3.3.2 如果使用reason r调用 rejectPromise ,则使用r作为reason拒绝promise。
                        reject(r);
                    });
                } catch (e) {
                    if (!state) {
                        // 2.3.3.3.4.2 否则 将上述的e作为 reason 拒绝promise.。
                        reject(e);
                    }
                }
            } else {
                // 2.3.3.4 如果 then 不是一个函数,则直接使用x作为value来实现promise.
                myResolve ? myResolve(x) : resolve(x);
            }
        } catch(e) {
            // 2.3.3.2 如果读取属性x.then导致抛出异常e,则以e作为reason拒绝promise。
            reject(e);
        }
    } else {
        // 2.3.4 如果x不是一个对象或者函数,则直接使用x作为value来实现promise
        myResolve ? myResolve(x) : resolve(x);
    }
}

function doOnFulfilled(promise, value, resolve, reject, onFulfilled) {
    if (typeof onFulfilled !== 'function') {
        resolve(value);
    } else {
        try{
            let x = onFulfilled(value);
            resolvePromise(promise, x, resolve, reject);
        }catch(e){
            reject(e);
        }
    }
}

function doOnRejected(promise, value, resolve, reject, onRejected) {
    if (typeof onRejected !== 'function') {
        reject(value);
    } else {
        try{
            let x = onRejected(value);
            resolvePromise(promise, x, resolve, reject);
        }catch(e){
            reject(e);
        }
    }
}

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

class MyPromise {
    #promiseState = PENDING;
    #promiseResult = '';
    #microTaskQueue = [];

    constructor(executor) {
        let myResolve = (value) => {
            let doMyResolve = () => {
                this.#promiseState = FULFILLED;
                this.#promiseResult = value;
                // 增加下面的逻辑
                this.#microTaskQueue.forEach(item => {
                    item.onFulfilled(value);
                });
            }
            if (this.#promiseState === PENDING) {
                resolvePromise(this, value, myResolve, myReject, doMyResolve);
            }
        }
        let myReject = (reason) => {
            if (this.#promiseState === PENDING) {
                this.#promiseState = REJECTED;
                this.#promiseResult = reason;
                // 增加下面的逻辑
                this.#microTaskQueue.forEach(item => {
                    item.onRejected(reason);
                });
            }
        }
        try {
            executor(myResolve, myReject);
        } catch (e) {
            this.#promiseState = REJECTED;
            this.#promiseResult = e;
        }
    }

    static resolve(value) {
         // 参数是一个 Promise 实例
        if (value instanceof MyPromise) {
            return value;
        }
        return new MyPromise((resolve, reject) => {
            resolve(value);
        });
    }

    static reject(reason) {
        return new MyPromise((resolve, reject) => {
            reject(reason);
        });
    }

    then(onFulfilled, onRejected){
        let newMyPromise = new MyPromise((resolve,reject) => {
            if (this.#promiseState === FULFILLED) {
                setTimeout(() => {
                    doOnFulfilled(newMyPromise, this.#promiseResult, resolve, reject, onFulfilled);
                }, 0)
            } if (this.#promiseState === REJECTED) {
                setTimeout(() => {
                    doOnRejected(newMyPromise, this.#promiseResult, resolve, reject, onRejected);
                }, 0)
            } else if (this.#promiseState === PENDING) {
                this.#microTaskQueue.push({
                    onFulfilled: () => {
                        setTimeout(() => {
                            doOnFulfilled(newMyPromise, this.#promiseResult, resolve, reject, onFulfilled);
                        }, 0)
                    },
                    onRejected: () => {
                        setTimeout(() => {
                            doOnRejected(newMyPromise, this.#promiseResult, resolve, reject, onRejected);
                        }, 0)
                    }
                })
            }
        });
        return newMyPromise;
    }

    catch(func) {
        return this.then(null, func);
    }

    finally(callback) {
        return this.then(
            // 在 Promise 成功时执行回调,并保持原 Promise 的状态和值不变
            value => MyPromise.resolve(callback()).then(() => value),
            // 在 Promise 失败时执行回调,并保持原 Promise 的状态和值不变
            reason => MyPromise.resolve(callback()).then(() => { throw reason; })
        );
    }

    // 下面是在Promise类上面的方法
    static race([]){}
    static all([]){}
    static any([]){}
    static allSettled([]){}
    static try(f){}
}

MyPromise.resolved = MyPromise.resolve;
MyPromise.rejected = MyPromise.reject;

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

4. 遗留问题

  1. 关于Promise.resolve和构造函数这里的实现,欢迎提出意见
  2. 其他方法之后再实现,肝不动了
相关推荐
hackeroink1 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者2 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-3 小时前
验证码机制
前端·后端
燃先生._.4 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235245 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240256 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar6 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人6 小时前
前端知识补充—CSS
前端·css