promise-polyfill-ts

1.0.1 • Public • Published

经常会有面试官问道“如何手动实现一个符合Promise/A+规范的简易版Promise”。当然通过面试不是我们的最终目的,熟练掌握其内部实现原理才是根本。
在写代码之前,我们需要知道Promise的三种状态,即pending, resoled, rejected。整个Promise的初始状态是pending,然后根据后续的处理情况状态会变为resoled或者rejected

enum Status {
    PENDING,
    RESOLVED,
    REJECTED
}

首先我们要弄清楚Promise的调用过程,在创建Promise的过程时,我们通常会new Promise(executor)。整个Promise的构造函数接受一个executor函数,该函数接收两个参数一个是resolve,一个是reject。
但是经过简单的分析我们可以知道resolve和reject也是两个函数,并且这两个函数应该是Promise类内部帮我们实现好的。

下面我们来看Promise内部需要哪些属性:

  • 我们需要一个存储当前Promise状态的status
  • 一个保存Promise成功时的值,方便我们传给resolve函数
  • 一个保存Promise失败时的值,方便我们传给reject函数
  • 一个存储Promise成功时回调函数的数组
  • 一个存储Promise失败时的回调函数的数组
private stateStatus = Status.PENDING;
private valueV = null;
private reasonR = null;
private resolvedCallbacksIStatusFunc<V>[] = [];
private rejectedCallbacksIStatusFunc<R>[] = [];

我们也需要定义Promise内部的resolve和reject函数,这两个函数即分别执行成功和失败的回调函数。

// 将当前Promise的状态修改为resolved
// 并执行resolve时的回调函数
let resolve: IStatusFunc<V> = (value) =>{
    if (this.state === Status.PENDING) {
        this.state = Status.RESOLVED;
        this.value = value;
        this.resolvedCallbacks.forEach(callback => {
            callback(value);
        });
    }
}
 
// 将Promise的状态修改为rejected
// 并执行reject时的回调函数
let reject: IStatusFunc<R> = (reason) => {
    if (this.state === Status.PENDING) {
        this.state = Status.REJECTED;
        this.reason = reason;
        this.rejectedCallbacks.forEach(callback => {
            callback(reason);
        });
    }
}

接下来就是实现Promise中最为重要的一步,then方法,它可以实现链式调用。我们知道在jQuery中,链式调用的方式是在方法的尾部返回jQuery对象this。但显然这种方式在Promise中行不通,因为Promise的主要任务是解决异步操作,异步操作里面的return是无效的。
我们首先想到的就是在then中返回一个Promise对象,这样返回的Promise就可以继续调用其原型链上的then方法。但此时,如果单纯的执行onFullfilled或者onRejected回调函数的话,此时是不会有resolve或者reject这个过程的,所以为了让Promise有链式调用的功能,我们就需要有一个专门解析onFullFilledonRejected返回值得函数

/**
 * 解析onFullfilled或者onRejected的返回值
 * @param promise2 then函数返回的新的Promise
 * @param x onFullfilled或者onRejected的返回值
 * @param resolve 成功时的回调
 * @param reject  失败时的回调
 */
private resolvePromise(promise2MyPromise<V, R>, xany, resolveIStatusFunc<V>, rejectIStatusFunc<R>) {
    // 禁止循环引用
    if (x === promise2) {
        throw new Error('Chaining cycle detected for promise');
    }
    // 是否已经调用过的标记
    let called: boolean = false;
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            // 如果x.then依旧是function,代表x是promise对象
            if (typeof x.then === 'function') {
                // x.then继续执行,第一个参数是this,后面依次是成功回调和失败的回调
                x.then.call(x, y => {
                    // 成功和失败只能调用一个
                    if (called) return;
                    called = true;
                    this.resolvePromise(promise2, y, resolve, reject);
                }, err => {
                    // 成功和失败只能调用一个
                    if (called) return;
                    called = true;
                    reject(err);
                });
            } else {
                resolve(x);
            }
        } catch(e) {
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        // 如果是普通类型,直接resolve
        resolve(x);
    }
}

then方法的实现也比较简单

/**
 * then方法
 * @param onFullfilled 成功的回调函数
 * @param onRejected 失败的回调函数
 */
then(onFullfilledIStatusFunc<V>, onRejected?: IStatusFunc<R>) {
 
    // 返回Promise的目的是为了链式调用
    let promise2 = new MyPromise<V, R>((resolve: IStatusFunc<V>, reject: IStatusFunc<R>) => {
        if (this.state === Status.RESOLVED) {
            let x = onFullfilled(this.value);
            this.resolvePromise(promise2, x, resolve, reject);
        }
        if (this.state === Status.REJECTED) {
            let x = onRejected(this.reason);
            this.resolvePromise(promise2, x, resolve, reject);
        }
        if (this.state === Status.PENDING) {
            this.resolvedCallbacks.push(() => {
                let x = onFullfilled(this.value);
                this.resolvePromise(promise2, x, resolve, reject);
            });
            this.rejectedCallbacks.push(() => {
                let x = onRejected(this.reason);
                this.resolvePromise(promise2, x, resolve, reject);
            });
        }
    });
    return promise2;
}

Package Sidebar

Install

npm i promise-polyfill-ts

Weekly Downloads

0

Version

1.0.1

License

MIT

Unpacked Size

23.4 kB

Total Files

5

Last publish

Collaborators

  • guolizhi