相关链接

代码实现

Promise 的状态

enum States {
    PENDING = "pending",    
    /**
     * PENDING 状态,此时该 Promise 的结果是悬而未决的,不知道会被 resolve,或是被 reject。
     * 只有当 Promise 的状态为 PENDING 时,resolve 和 reject 函数才可以执行一系列操作,否则只会抛出一个错误。
     **/
    FULFILLED = "fulfilled",    
    /**
     * FULFILLED 状态,说明此时 Promise 已经被 resolve,“承诺实现了”。
     * 当 Promise 的状态为 FULFILLED 时,then 方法传入的 onFullfilled 函数才能直接加入微任务队列;
     * 若当前 Promise 的状态为 PENDING,onFullfilled 函数只能加入成功时的回调函数队列。
     **/
    REJECTED = "rejected",
    /**
     * REJECTED 状态,说明此时 Promise 已经被 reject,
     * 当 Promise 的状态为 REJECTED 时,then 方法传入的 onRejected 函数才能直接加入微任务队列;
     * 若当前 Promise 的状态为 PENDING,onRejected 函数只能加入失败时的回调函数队列。
     **/
}

相关类型定义

interface Resolve<T> {
    (value: T | PromiseLike<T>): void;
}

interface Reject {
    (reason?: any): void;
}

interface Executor<T> {
    (resolve: Resolve<T>, reject: Reject): void;
}

/**
 * Promises/A+ 只是一份规范,任何能通过测试的 Promise 实现都会被这份规范认可,而一些库和框架会实现自己的 Promise,而不是使用原生 ES6 Promise,这就导致了无法直接使用 p instanceof Promise 来识别 Promise类型。
 * 因此识别 Promise 是基于鸭子类型(duck typing)检测的,只要是一个 thenable 对象(即定义了 then 方法的对象),即会被识别为 Promise。
 * 同理,下文中 resolvePromise 函数的参数 x 是 PromiseLike 类型而不是 Promise 类型。
 **/
interface PromiseLike<T> {
    then<TResult1 = T, TResult2 = never>(
        onFulfilled?: ((value: T | PromiseLike<T>) => TResult1 | PromiseLike<TResult1>) | undefined | null,
        onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
    ): PromiseLike<TResult1 | TResult2>;
}

MyPromise 类及其构造函数

Promise 接收一个执行器函数作为参数,该执行器函数的两个参数 (resolve, reject) 由 Promise 内部定义。

class MyPromise<T> {
    private state: States = States.PENDING;        //    Promise 的状态
    private onFulfilledCbs = [] as (() => void)[]; //    成功时的回调函数
    private onRejectedCbs = [] as (() => void)[];  //    失败时的回调函数
    private value!: T | PromiseLike<T>;            //    该 Promise 传递的值,是一个合法的 JavaScript 值
    private reason: any;                           //    表示该 Promise 失败原因的值

    constructor(executor: Executor<T>) {
        try {
            //    作为参数传递给 Promise 的 executor 函数会被立即调用
            executor(this.resolve, this.reject);
        } catch (e) {
            this.reject(e);
        }
    }

    resolve: Resolve<T> = (value: T | PromiseLike<T>) => {
        try {
            //    由于 js 并没有提供直接用来创建微任务的 api,因此这里用setTimeout模拟创建微任务
            setTimeout(() => {
                if (this.state === States.PENDING) {
                    /**
                     * 仅当 Promise 处于 PENDING 状态时,会执行 resolve 方法的实际逻辑。
                     * resolve 方法会做以下几件事:
                     * 1. 将 Promise 的状态更新为 FULFILLED;
                     * 2. 将 resolve 接受的 value 值赋值给 Promise 的 value;
                     * 3. 执行 onFulfilledCbs 队列中的所有回调函数,并清空该队列。
                     **/ 
                    this.state = States.FULFILLED;
                    this.value = value;
                    this.onFulfilledCbs.forEach((fn) => fn());
                    this.onFulfilledCbs = [];
                }
            });
        } catch (e) {
            this.reject(e);
        }
    };

    reject: Reject = (reason: any) => {
        try {
            //  用setTimeout模拟创建微任务
            setTimeout(() => {
                if (this.state === States.PENDING) {
                    /**
                     * 仅当 Promise 处于 PENDING 状态时,会执行 reject 方法的实际逻辑。
                     * reject 方法会做以下几件事:
                     * 1. 将 Promise 的状态更新为 REJECTED;
                     * 2. 将 reject 接受的 reason 值赋值给 Promise 的 reason;
                     * 3. 执行 onRejectedCbs 队列中的所有回调函数,并清空该队列。
                     **/ 
                    this.state = States.REJECTED;
                    this.reason = reason;
                    this.onRejectedCbs.forEach((fn) => fn());
                    this.onRejectedCbs = [];
                }
            });
        } catch (e) {
            this.reject(e);
        }
    };
}

then 方法

then 方法接收两个可选参数,分别为 Promise 的成功和失败情况的回调函数。
这两个函数不会立即被调用,具体情况见代码注释。

then<TResult1 = T, TResult2 = never>(
    onFulfilled?: ((value: T | PromiseLike<T>) => TResult1 | PromiseLike<TResult1>) | undefined | null,
    onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
): MyPromise<TResult1 | TResult2> {
    //    为确保 onFulfilled 和 onRejected 是函数类型,需要做一个类型转换。
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (val: T | PromiseLike<T>) => val as any;
    onRejected =
        typeof onRejected === "function"
            ? onRejected
            : (r: any) => {
                  throw r;
              };
    //    then 方法的返回值是一个新的 Promise,这是 Promise 能够链式调用的原因
    const promise2 = new MyPromise<TResult1 | TResult2>((resolve: Resolve<TResult1 | TResult2>, reject: Reject) => {
        if (this.state === States.FULFILLED) {
            //    Promise 已处于 FULFIILED 状态,将 onFulfilled 放入微任务队列中执行。
            setTimeout(() => {
                try {
                    let x = onFulfilled!(this.value);
                    this.resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        } else if (this.state === States.REJECTED) {
            //    Promise 已处于 REJECTED 状态,将 onRejected 放入微任务队列中执行。

            setTimeout(() => {
                try {
                    let x = onRejected!(this.reason);
                    this.resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        } else if (this.state === States.PENDING) {
            /**
             * Promise 仍处于 PENDING 状态,尚不能处理回调函数,直接将回调函数加入相应的回调队列。
             * 注意,resolve 中调用回调函数是在微任务中进行的,因此这里不再需要创建微任务。
             **/
            this.onFulfilledCbs.push(() => {
                try {
                    let x = onFulfilled!(this.value);
                    this.resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
            this.onRejectedCbs.push(() => {
                try {
                    let x = onRejected!(this.reason);
                    this.resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        }
    });
    return promise2;
}

resolvePromise 方法

resolvePromise<T>(promise: MyPromise<T>, x: T | PromiseLike<T>, resolve: Resolve<T>, reject: Reject) {
    /**
     * 由于 then 方法的返回值是一个 Promise,如果 then 方法的回调函数同样返回了 Promise,那么就会造成 Promise 的嵌套,因此需要有一个函数对 then 方法回调函数的返回值进行处理,resolvePromise 就是这个函数。
     * @param promise then 方法的返回值
     * @param x then 回调函数的返回值
     **/
    
    //    防止循环引用
    if (promise === x) {
        const e = new TypeError("TypeError: Circular reference");
        reject(e);
    }

    let called = false; //  防止 resolve 和 reject 多次调用
    if (x && (typeof x === "object" || typeof x === "function")) {
        try {
            const then = (x as PromiseLike<T>).then;
            //    如果 x 是一个 thenable
            if (typeof then === "function") {
                then.call(
                    x,
                    (y: T | PromiseLike<T>) => {
                        if (called) return;
                        called = true;
                        //  直到解析的对象不再是 thenable,取出其中的值
                        this.resolvePromise(promise, y, resolve, reject);
                    },
                    (r: any) => {
                        if (called) return;
                        called = true;
                        reject(r);
                    }
                );
            } else {
                resolve(x);
            }
        } catch (e) {
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}

测试流程

测试工具文档:Promises/A+ Compliance Test Suite

为了使用官方工具进行测试,需要在程序的最后添加如下代码:

// @ts-ignore
MyPromise.deferred = function () {
    let deferred: any = {};
    deferred.promise = new MyPromise((resolve, reject) => {
        deferred.resolve = resolve;
        deferred.reject = reject;
    });
    return deferred;
};

// @ts-ignore
export = MyPromise;

将ts代码编译为js

tsc promise.ts

运行测试

npx promises-aplus-tests promise.js

测试结果:测试通过

参考文章

  1. Promise的源码实现(完美符合Promise/A+规范)
  2. 实现一个TS版,符合 Promise/A+规范的 Promise
  3. 《你不知道的 JavaScript 中卷》

知白守黑
1 声望0 粉丝