【手写】Promise

368 阅读3分钟

手写系列作为当下面试中的常青树,如果不能掌握常见的手写方法,怎么能很好的通过面试呢! Promise 对象用于表示一个异步操作的最终完成(或失败)及其结果值。它有三个状态,分别为pending(等待态)、fulfilled(完成态)和rejectd(拒绝态)。这三个状态的转换关系为pending -> fulfilled、pending -> rejected,即只能由pending态转换到其他态。

Promise的使用方法为:

 let promise = new Promise((resolve, reject) => {
     setTimeout(() => {
         resolve(12);     
     }, 1000)
 })
 
 promise.then(val => {
     console.log(val);
 }, reason => {
     console.log(reason);
 })

从最简单的写起,即传递给Promise的函数是立即执行并有结果产生的,不考虑异步。有如下代码:

class MyPromise {
    STATUS = 'PENDING';
    val = undefined;
    reason = undefined;
    constructor(executor) {
        executor(this._resolve.bind(this), this._reject.bind(this));
    }

    _resolve(val) {
        // 只有pending状态才能转换到resolved状态
        if (this.STATUS !== 'PENDING') {
            return;
        }
        this.STATUS = 'RESOLVED';
        this.val = val;
    }

    _reject(reason) {
         // 只有pending状态才能转换到rejected状态
         if (this.STATUS !== 'PENDING') {
             return;
         }
        this.STATUS = 'REJECTED';
        this.reason = reason;
    }

    /**
     * promise.then的特点
     * 1. 接收两个函数,这两个函数都是可选的,第一个函数处理异步任务执行成功的情况,第二个函数处理异步任务执行失败的情况
     * 2. then返回的是一个新的Promise对象,因此.then可以链式调用
     * 3. 后面的.then如果需要处理异步任务返回的结果,则需要前面的then将该结果return出去
     */
    then(onFulFilled, onRejected) {
        if (typeof onFulFilled !== 'function') {
            return new MyPromise((resolve, reject) => {
                resolve(this.val);
            })
        }
        if (this.STATUS === 'RESOLVED') {
            return new MyPromise((resolve, reject) => { 
                const res = onFulFilled(this.val);
                resolve(res);
            })
        }

        if (this.STATUS === 'REJECTED') {
            return new MyPromise((resolve, reject) => {
                if (typeof onRejected !== 'function') {
                    reject(this.reason);
                    return;
                }
                const reason = onRejected(this.reason);
                resolve(reason);
            })
        }
    }

    catch(onCatch) {
        onCatch(this.reason);
    }
}

测试代码:

const promise = new MyPromise((resolve, reject) => {
    resolve(1234)
});

const temp = promise.then().then().then(res => {
    console.log('then3: ', res);
}).catch(reason => {
    console.log('catch: ', reason);
})

经过测试,能正常打印内容。但是如果加上异步函数在里面,则打印的内容就不正确了。而在使用Promise的场景中,异步应该是占绝大多数的,因此要加上处理异步的逻辑。

写完上面的代码,本以为对手写Promise自己还是掌握的不少了,但是在后续的测试中发现还有很多情况下执行的结果和预期的不符。在then方法中也只处理了resolved和rejected的情况,对于pending的情况没有做处理,于是,看了在MDN上Promise的介绍和其他大神的文章,在确保自己已经理解清楚后,有如下改版代码:

class MyPromise {
    STATUS = 'PENDING';
    val = undefined;
    reason = undefined;
    onFullFilledCallbacks = [];
    onRejectedCallbacks = [];
    constructor(fn) {
        fn(this._resolve.bind(this), this._reject.bind(this))
    }

    _resolve(val) {
        // 只有pending状态才能转换到resolved状态
        if (this.STATUS !== 'PENDING') {
            return;
        }
        setTimeout(() => {
            this.STATUS = 'RESOLVED';
            this.val = val;
            this.onFullFilledCallbacks.forEach(callback => {
                callback(val);
            })
        })
    }

    _reject(reason) {
         // 只有pending状态才能转换到rejected状态
         if (this.STATUS !== 'PENDING') {
             return;
         }
        setTimeout(() => {
            this.STATUS = 'REJECTED';
            this.reason = reason;
            this.onRejectedCallbacks.forEach(callback => {
                callback(reason);
            })
        })
    }

    /**
     * promise.then的特点
     * 1. 接收两个函数,这两个函数都是可选的,第一个函数处理异步任务执行成功的情况,第二个函数处理异步任务执行失败的情况
     * 2. then返回的是一个新的Promise对象,因此.then可以链式调用
     * 3. 后面的.then如果需要处理异步任务返回的结果,则需要前面的then将该结果return出去
     */
    then(onFulFilled, onRejected) {
        onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => {
            throw reason;
        }
        if (this.STATUS === 'PENDING') {
            this.onFullFilledCallbacks.push(onFulFilled);
            this.onRejectedCallbacks.push(onRejected);
        }
        if (this.STATUS === 'RESOLVED') {
            return new MyPromise((resolve, reject) => { 
                const res = onFulFilled(this.val);
                resolve(res);
            })
        }

        if (this.STATUS === 'REJECTED') {
            return new MyPromise((resolve, reject) => {
                if (typeof onRejected !== 'function') {
                    reject(this.reason);
                    return;
                }
                const reason = onRejected(this.reason);
                resolve(reason);
            })
        }
    }

    catch(onCatch) {
        onCatch(this.reason);
    }
}

测试代码如下:

console.log('start');

const promise = new MyPromise((resolve, reject) => {
    console.log(123);
    resolve(1234);
    console.log('3456')
});

const temp = promise.then(res => {
    console.log('then1: ', res);
})
const temp2 = promise.then(res => {
    console.log('then2: ', res);
})
const temp3 = promise.then(undefined, reason => {
    console.log('rejected: ', reason);
})

console.log('end');

打印也是符合预期的。经过如上代码,对于基本的情况都是通过的,但是还有一个很重要的情况没有考虑,那就是then的链式调用。对于then链式调用的逻辑之前一直以为的是只要返回的是新的Promise,那就自然的可以链式调用了。但是在看了一个大神的解析Promise的文章后,深受启发,还有Promise/A+等知识点没有完全理透,因此后续的代码留待掌握了这块知识点后再完善。