promise实现从0到1(附源码)

174 阅读9分钟

文章摘要:

  • 一、PromiseA+规范
    • 1.术语
      • Promise
      • thenable
      • value
      • reason
      • exception
    • 2.规范细则
      • 三种状态及流转关系
      • then方法规范细则
  • 二、按规范一对一实现
    • 0.整体结构
    • 1.构造函数实现
    • 2.实现resolve方法
    • 3.实现reject方法
    • 4.实现then方法
    • 5.实现catch方法
    • 6.实现Promise.resolve
    • 7.实现Promise.reject
    • 8.实现Promise.race
    • 9.实现Promise.all
  • 三、测试程序
  • 四、源码

promise难点其实就是各种细节、边界条件。我们研究探索他的,其实不必拘泥这些细节,抓住核心:状态流转 是关键!什么的情况下有什么样的行为,内部是怎么流转的。

一、PromiseA+规范

讲解PromiseA+规范前,先了解些术语。

1.术语

  • promise 是一个有then方法的对象或者是函数,行为遵循 PromiseA+规范。
  • thenable 是一个有then方法的对象或者是函数。xxxable 就是有 xxx 方法(能力)的对象或是函数,一般规范里会有这个术语。
  • value 是promise状态成功时的值,也就是resolve的参数,可以是各种数据类型, 也包括undefined/thenable或者是 promise。
  • reason 是promise状态失败时的值, 也就是reject的参数, 表示拒绝的原因。
  • exception 是一个使用throw抛出的异常值。

2.规范

接下来分几部分来讲解PromiseA+规范.

2.1 Promise States

promise应该有三种状态. 要注意他们之间的流转关系.

  1. pending

    • 1.1 初始的状态, 可改变.
    • 1.2 一个promise在resolve或者reject前都处于这个状态。
    • 1.3 可以通过 resolve -> fulfilled 状态;
    • 1.4 可以通过 reject -> rejected 状态;
  2. fulfilled

    • 2.1 最终态, 不可变.
    • 2.2 一个promise被resolve后会变成这个状态.
    • 2.3 必须拥有一个value值
  3. rejected

    • 3.1 最终态, 不可变.
    • 3.2 一个promise被reject后会变成这个状态
    • 3.3 必须拥有一个reason

Tips: 总结一下, 就是promise的状态流转是这样的

pending -> resolve(value) -> fulfilled
pending -> reject(reason) -> rejected

2.2 then

promise应该提供一个then方法, 用来访问最终的结果, 无论是value还是reason.

promise.then(onFulfilled, onRejected)
  1. 参数要求

    • 1.1 onFulfilled 必须是函数类型, 如果不是函数, 应该被忽略.
    • 1.2 onRejected 必须是函数类型, 如果不是函数, 应该被忽略.
  2. onFulfilled 特性

    • 2.1 在promise变成 fulfilled 时,应该调用 onFulfilled, 参数是value
    • 2.2 在promise变成 fulfilled 之前, 不应该被调用.
    • 2.3 只能被调用一次(所以在实现的时候需要一个变量来限制执行次数)
  3. onRejected 特性

    • 3.1 在promise变成 rejected 时,应该调用 onRejected, 参数是reason
    • 3.2 在promise变成 rejected 之前, 不应该被调用.
    • 3.3 只能被调用一次(所以在实现的时候需要一个变量来限制执行次数)
  4. onFulfilled 和 onRejected 应该是微任务

    • 这里用queueMicrotask来实现微任务的调用.
  5. then方法可以被调用多次

    • 5.1 promise状态变成 fulfilled 后,所有的 onFulfilled 回调都需要按照then的顺序执行, 也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个onFulfilled的回调)
    • 5.2 promise状态变成 rejected 后,所有的 onRejected 回调都需要按照then的顺序执行, 也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个onRejected的回调)
  6. 返回值

    • then 应该返回一个promise
    promise2 = promise1.then(onFulfilled, onRejected);
    
    • 6.1 onFulfilled 或 onRejected 执行的结果为x, 调用 resolvePromise( 这里大家可能难以理解, 可以先保留疑问, 下面详细讲一下resolvePromise是什么东西 )

    • 6.2 如果 onFulfilled 或者 onRejected 执行时抛出异常e, promise2需要被reject

    • 6.3 如果 onFulfilled 不是一个函数, promise2 以promise1的value 触发fulfilled

    • 6.4 如果 onRejected 不是一个函数, promise2 以promise1的reason 触发rejected

  7. resolvePromise

    resolvePromise(promise2, x, resolve, reject)
    
    • 7.1 如果 promise2 和 x 相等,那么 reject TypeError

    • 7.2 如果 x 是一个 promsie 如果x是pending态,那么promise必须要在pending,直到 x 变成 fulfilled or rejected. 如果 x 被 fulfilled, fulfill promise with the same value. 如果 x 被 rejected, reject promise with the same reason.

    • 7.3 如果 x 是一个 object 或者 是一个 function let then = x.then. 如果 x.then 这步出错,那么 reject promise with e as the reason. 如果 then 是一个函数,then.call(x, resolvePromiseFn, rejectPromise) resolvePromiseFn 的 入参是 y, 执行 resolvePromise(promise2, y, resolve, reject); rejectPromise 的 入参是 r, reject promise with r. 如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。 如果调用then抛出异常e 如果 resolvePromise 或 rejectPromise 已经被调用,那么忽略 则,reject promise with e as the reason 如果 then 不是一个function. fulfill promise with x.

    这段描述晦涩难懂!可以结合resolvePromise函数具体的实现看会好很多.

二、按照规范代码实现

- 0.整体结构
- 1.构造函数实现
- 2.实现resolve方法
- 3.实现reject方法
- 4.实现then方法
- 5.实现catch方法
- 6.实现Promise.resolve
- 7.实现Promise.reject
- 8.实现Promise.race
- 9.实现Promise.all

0.整体结构

class MPromise {
    //1.init过程
    constructor(fn) {

    };
    get status(){};
    set status(newStatus){};
    
    //2.实现原型方法
    resolve(value){};
    reject(reason){};
    then(onFulfilled,onRejected){};
    catch(onRejected);
    
    //3.实现工具方法
    static resolve(value){};
    static reject(reason){};
    static race(promiseList){};
    static all(promiseList){}
}

平常⽤promise的时候, 是通过new关键字来new Promise(fn), 所以咱们应该⽤构造函数或者class 来实现,这里用ES6的class形式。

1.构造函数实现

  • 入参是一个函数fn,函数接收resolve 和 reject 两个参数。
  • 初始化promise时,就要执行这个函数。有任何报错通过reject抛出去。
// 定义3种状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MPromise { 
    //1.init过程
    constructor(fn) {
        // 1.0 变量声明
        FULFILLED_CALLBACK_LIST = [];
        REJECTED_CALLBACK_LIST = [];
        _status = PENDING;
        
        // 1.1 初始状态为pending
        this.status = PENDING;
        this.value = null;
        this.reason = null;
        
        // 1.2 绑定好this
        fn(this.resolve.bind(this), this.reject.bind(this));

    } ;
    
    //2.用es6的getter/setter更符合语义;当status值变化时,执行所有回调。
    get status() {
        return this._status;
    }

    set status(newStatus) {
        this._status = newStatus;
        switch (newStatus) {
            case FULFILLED: {
                this.FULFILLED_CALLBACK_LIST.forEach(callback => {
                    callback(this.value);
                });
                break;
            }
            case REJECTED: {
                this.REJECTED_CALLBACK_LIST.forEach(callback => {
                    callback(this.reason);
                });
                break;
            }
        }
    };
    
   isFunction(param) {
        return typeof param === 'function';
    };
}

2.实现resolve方法

  • 根据规范,这两个方法的职责是要改变status的。从pending -> fulfilled/rejected。
  • 注意两个函数的入参分别是value 和 reason。
    resolve(value) {
        if (this.status === PENDING) {
            this.value = value;
            this.status = FULFILLED;
        }
    };

3.实现reject方法

    reject(reason) {
        if (this.status === PENDING) {
            this.reason = reason;
            this.status = REJECTED;
        }
    };

接下来实现一下关键的then方法。

4.实现then方法

  • then接收2个参数:onFulfilled 和 onRejected。
  • onFulfilled 和 onRejected 是微任务,这里用原生的queueMicrotask包裹执行函数。
  • 核心:订阅发布模式 + 对回调进行微任务的包裹。
  • 下面的代码,你按照注释标记顺序去看。 ps: "微任务",可以简单理解成:在异步队列里提升函数执行的优先级。

    then(onFulfilled, onRejected) {
        //1.参数处理:如果不是函数就原样返回value或是reason->对应规范里的7.2、7.3
        const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
            return value
        }
        const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
            throw reason;
        };
        //2.要知道 then 返回值是一个新的promise,所以咱得用promise来包裹一下,最后return promise2
        const promise2 = new MPromise((resolve, reject) => {
            //4.如果onFulfilled 或 onRejected 抛出一个异常e,则promise2必须拒绝执行,并返回拒绝原因e。遇到这种规范,我们就需要给对应语句执行包裹上try catch,遇到报错就reject
            const fulfilledMicrotask = () => {
                queueMicrotask(() => {
                    try {
                        //对应规范里的7.4:如果onFulfilled 或 onRejected 返回一个值 x,则运行 resolvePromise
                        const x = realOnFulfilled(this.value);
                        this.resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            };
            const rejectedMicrotask = () => {
                queueMicrotask(() => {
                    try {
                        const x = realOnRejected(this.reason);
                        this.resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            }
            //3.根据当前的promise的状态,调用不同的函数
            switch (this.status) {
                case FULFILLED: {
                    fulfilledMicrotask()
                    break;
                }
                case REJECTED: {
                    rejectedMicrotask()
                    break;
                }
                case PENDING: {
                     //核心:订阅发布模式,定义数组收集依赖,某个时机去挨个通知依赖们。
                     //核心:实现异步的关键:用数组收集到成功和失败的所有callback,调用then时候,如果还是异步没执行完还是处于pending状态就存入数组。等status状态改写的那一刻再去执行callback。
                    this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask)
                    this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask)
                }
            }
        })
        return promise2

    };

这里的 resolvePromise 就是规范里看着晦涩难懂地方。实现代码多,这里为了阅读体验上能直观连贯,放到最后。

5.实现catch方法


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

6.实现Promise.resolve

  • 将现有对象转为 Promise 对象。
  • 注意是一个静态方法(又叫工具方法);咱调用时候是Promise.resolve,而不是通过实例去调用的。
  • 参数value如果不是具有then方法的对象(又称thenable对象),则返回一个新的Promise对象,且它的状态为fulfilled。
    static resolve(value) {
        if (value instanceof MPromise) {
            return value;
        }

        return new MPromise((resolve) => {
            resolve(value);
        });
    };

7.实现Promise.reject

  • 将现有对象转为 Promise 对象。
  • 注意是一个静态方法。
  • 返回一个新的Promise实例,该实例的状态为rejected。参数reason会被传递给实例的回调函数。

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

8.实现Promise.race

  • "race":"竞速",看谁跑的快,就用谁的结果。
  • 使用:const p = Promise.race([p1,p2,p3])。
  • 此方法是将多个Promise实例,包装成一个新的Promise实例。
  • 只有p1/p2/p3 之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就会传递给p的回调函数。for里的return是关键。
    static race(promiseList) {
        return new MPromise((resolve, reject) => {
            const length = promiseList.length;

            if (length === 0) {
                return resolve();
            } else {
                for (let i = 0; i < length; i++) {
                    MPromise.resolve(promiseList[i]).then(
                        (value) => {
                            return resolve(value);
                        },
                        (reason) => {
                            return reject(reason);
                        });
                }
            }
        });

    };

9.实现Promise.all

    static all (promiseList) {
        return new MPromise((resolve, reject) => {
     
            let values = []// 返回值的集合
            let count = 0
            for (let [i, p] of promiseList.entries()) {
              MPromise.resolve(p).then(res => {
                values[i] = res
                count++
                // 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
                if (count === promiseList.length) resolve(values)
              }, err => {
                // 有一个被rejected时返回的MyPromise状态就变成rejected
                reject(err)
              })
            }
        })
    }
   

10. resolvePromise


    resolvePromise(promise2, x, resolve, reject) {
        // 如果 newPromise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 newPromise
        // 这是为了防止死循环
        if (promise2 === x) {
            return reject(new TypeError('The promise and the return value are the same'));
        }

        if (x instanceof MPromise) {
            // 如果 x 为 Promise ,则使 newPromise 接受 x 的状态
            // 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
            queueMicrotask(() => {
                x.then((y) => {
                    this.resolvePromise(promise2, y, resolve, reject);
                }, reject);
            })
        } else if (typeof x === 'object' || this.isFunction(x)) {
            // 如果 x 为对象或者函数
            if (x === null) {
                // null也会被判断为对象
                return resolve(x);
            }

            let then = null;

            try {
                // 把 x.then 赋值给 then 
                then = x.then;
            } catch (error) {
                // 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
                return reject(error);
            }

            // 如果 then 是函数
            if (this.isFunction(then)) {
                let called = false;
                // 将 x 作为函数的作用域 this 调用
                // 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
                try {
                    then.call(
                        x,
                        // 如果 resolvePromise 以值 y 为参数被调用,则运行 resolvePromise
                        (y) => {
                            // 需要有一个变量called来保证只调用一次.
                            if (called) return;
                            called = true;
                            this.resolvePromise(promise2, y, resolve, reject);
                        },
                        // 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
                        (r) => {
                            if (called) return;
                            called = true;
                            reject(r);
                        });
                } catch (error) {
                    // 如果调用 then 方法抛出了异常 e:
                    if (called) return;

                    // 否则以 e 为据因拒绝 promise
                    reject(error);
                }
            } else {
                // 如果 then 不是函数,以 x 为参数执行 promise
                resolve(x);
            }
        } else {
            // 如果 x 不为对象或者函数,以 x 为参数执行 promise
            resolve(x);
        }
    };

三、测试程序

const test = new MPromise((resolve, reject) => {
    setTimeout(() => {
        // resolve('918元');
        reject('网络超时!')
    }, 1000);
}).then((value) => {
    console.log('成功-then:'+value);
}).catch((reason) => {
    console.log('失败-catch:'+reason);
})

cmd 里执行下当前js文件

$ node promise.js

失败-catch:网络超时!

四、源码附件

gitee源码