ES6之手写Promise

92 阅读3分钟

ES6之手写Promise

接下来,将一步步手写一个 Promise ,将实现一个 Promise 的基本功能。

const PENDING='pending';
const FULFILLED='fulfilled';
const REJECTED='rejected';
class MyPromise {
    /**
     * 
     * @param {Function} executor 构造器,创建一个Promise对象
     */
    constructor(executor) {
        this._state = PENDING;
        this._value = undefined;
        try {
            executor(this._resolve.bind(this), this._reject.bind(this));
        } catch (error) {
            this._reject(error);
        }
    }
    /**
     * 
     * @param {String} newState 改变状态
     * @param {any} value 改变相关数据
     * @returns 
     */
    _changeState(newState, value) {
        if (this._state !== PENDING) {
            return;
        }
        this._state = newState;
        this._value = value;
    }
    /**
     * 
     * @param {any} data 成功数据 
     * @returns 
     */
    _resolve = (data) => this._changeState(FULFILLED, data)
    /**
     * 
     * @param {any} reason 失败数据
     * @returns 
     */
    _reject = (reason) => this._changeState(REJECTED, reason)
}

const myP = new MyPromise((resolve, reject) => {
    throw new Error('出错了');
    resolve('洗完澡了');
    resolve('喝完水了')
})
console.log(myP)

创建一个 MyPromise 的类,构造器中将状态和相关数据放进去。因为 resolve 和 reject 就是改变状态和数据,所以创建一个改变状态和数据的辅助函数 changeState ,状态确定之后不可改变,所以在 changeState 中要判断后面调用不会再进行赋值。同时,如果报错也会直接调用 reject 函数。

/**
 * 运行微任务,将传递来的函数放到微队列之中
 * @param {Function} callback 
 */
function runMicroTask(callback) {
    if (process && process.nextTick) {
        process.nextTick(callback);
    } else if (MutationObserver) {
        //只要内部元素一变化,它将一个回调函数放入微队列中
        const p = document.createElement('p');
        const observer = new MutationObserver(callback);
        observer.observe(p, {
            childList: true
        });
        p.innerHTML = '1';
    } else {
        setTimeout(callback, 0);
    }
}

这个辅助函数实现了将一个回调函数放到微队列之中。可以分为 node 环境和浏览器环境。node 环境使用 nextTick 就可以了,浏览器环境需要建立一个观察器,观察元素内部的变化,内部一变化,MutationObserver 就将一个回调函数放到微队列中。如果还有其他的情况,就将回调函数放到计时器中。

/**
     * 处理队列中加一个then相关的对象
     * @param {Function} executor then执行的函数 
     * @param {String} state 执行时要求的状态
     * @param {Function} resolve 执行的成功回调
     * @param {Function} reject 执行的失败回调
     */
    _pushHandler(executor, state, resolve, reject) {
        this._handlers.push({ executor, state, resolve, reject });
    }
    /**
     * 
     * @param {Function} onFulfilled 成功的回调
     * @param {Function} onRejected 失败的回调
     * @returns 
     */
    then(onFulfilled, onRejected) {
        return new MyPromise((resolve, reject) => {
            this._pushHandler(onFulfilled, FULFILLED, resolve, reject);
            this._pushHandler(onRejected, REJECTED, resolve, reject);
        })
    }

我们先把要执行的 then 函数放到一个队列中,在执行时再依次执行。创建一个辅助函数 pushHandler ,将函数,状态,成功和失败的函数回调。在执行 then 函数时,将一个对象传进去。

/**
     * 执行队列
     */
    _runHandlers() {
        if (this._state === PENDING) {
            return;
        }
        while (this._handlers[0]) {
            const handler = this._handlers[0];
            this._runOneHandler(handler)
            this._handlers.shift();
        }
    }

前面调用 then 的时候会放到一个队列里,这个函数开始遍历每个元素,然后放到处理单个 handler 的函数中,注意:使用 shift() 删除第一个元素的时候,要使用 while 循环,使用 for 循环会出现指向错误。在状态变化的时候和 then 方法的时候都需要调用。

/**
 * 判断一个对象是否是Promise对象
 * @param {any} obj 
 * @returns 
 */
function isPromise(obj) {
    return !!(obj && typeof obj === 'object' && typeof obj.then === 'function');
}
/**
     * 执行一个队列
     * @param {Object} handler 队列对象
     */
    _runOneHandler({ executor, state, resolve, reject }) {
        runMicroTask(() => {
            if (this._state !== state) {
                //状态不一致
                return;
            } 
            if (typeof executor !== 'function') {
                //then里面传递的不是函数类型
                this._state === FULFILLED ? resolve(this._value) : reject(this._value);
                return;
            }
            try {
                //函数返回的结果
                const result = executor(this._value);
                console.log(result);
                if (isPromise(result)) {
                    return result.then(resolve, reject);
                } else {
                    resolve(result);
                }
            } catch (error) {
                reject(error);
                console.error(error);
            }
        });
    }

现在要把每一个对象放到微队列里面。先要判断状态是否一致,不一致跳过,判断是否是一个函数,不是函数会发生状态穿透,保持原来的状态和数据。当 handler 在执行的时候,如果没有报错,就像返回的数据作为成功的结果,如果报错,就作为失败的原因。如果返回的是一个 Promise ,满足 A+ 规范,则和这个 Promise 保持一致,它成功最后就成功,反之亦然。