Promise 封装(Es6) 面试手写代码必备(配合注解)

69 阅读4分钟
  • 面试时经常会碰到手写Promise的面试题,在此记录一下

  • 整体思路

      1. Promise 是一个类,在执行这个类的时候,需要传递一个执行器进去,执行器就会立即执行;
      1. Promise 有三种状态, 成功 - Fullfilled; 失败 - Rejected; 等待 - Pending;并且promise最终只会出行两种结果, Pending -> Fullfilled; Pending -> Rejected;而且promise的状态一旦确定不可更改;
      1. resolve 和 rejected 是用来更改状态的, 需要两个相对应的方法;
      1. then 方法内部做的最主要的事情就是判断状态。 成功走成功的回调,失败走失败的回调。
      1. 如果 then 中有异步操作,那么状态就会为Pending,所以需要把回调存起来; 在resolve 和 reject的时候去检测, 是否存在成功失败的回调,存在就调用并且把值传进去。
      1. 如果多次调用then, 并且包含异步操作, 我们需要把成功 / 失败 的回调存储起来放到一个数组中, 在resolve / reject 时利用 while 循环调用, 判断数组的 length 是否存在, 存在调用shift()方法, 把第一个拿出来, 传入相应的值。 MyPromise.js
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
    constructor (callback) {
        // 利用try catch 来捕获错误,如果报错了就直接调用reject把错误反出去 
        try {
            callback(this.resolve, this.reject);
        } catch (err) {
            this.reject(err)
        }
    }
    
    status = PENDING; // 状态默认是pending;

    // 保存值
    successVal = undefined;
    failVal = undefined;

    // 保存成功失败的函数
    successCallback = [];
    failCallback = [];
    
    /**
     * 这里resolve 和 reject 使用箭头函数是为了this指向上下文MyPromise,
     * function也可以,但是需要改变this指向
     */
    resolve = (successVal) => {
        // resolve和reject只能从pending -> resolve, pending -> reject
        if (this.status !== PENDING) return;
        this.status = FULFILLED;
        this.successVal = successVal;
        while(this.successCallback.length) this.successCallback.shift()();
    }

    reject = (failVal) => {
        if (this.status !== PENDING) return;
        this.status = REJECTED;
        this.failVal = failVal;
        while(this.successCallback.length) this.successCallback.shift()();
    }

    then (successCallback, failCallback) {
        /**
         * .then 方法是可以链式调用的,比如 .then().then().then(val => console.log(val)),
         * 这个时候就需要把没有做操作的then中的值return给下一个then
         */
        successCallback = successCallback ? successCallback : successVal => successVal;
        failCallback = failCallback ? failCallback : failVal => {throw failVal};

        // 由于promise的 .then 方法返回一个promise,所以这里也返回一个mypromise;
        let promiseBack = new MyPromise((resolve, reject) => {
            if (this.status === FULFILLED) {
                // 因为调用checkPromise的时候拿不到promiseBack, 所以调用seTTimeout 把代码变成异步的
                setTimeout(() => {
                    try {
                        let result = successCallback(this.successVal);
                        checkPromise(promiseBack, result, resolve, reject);  
                    } catch (e) {
                        reject(e);
                    }
                    
                }, 0);
            } else if (this.status === REJECTED) {
                setTimeout(() => {
                    try {
                        let result = failCallback(this.failVal);
                        checkPromise(promiseBack, result, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                 
                }, 0);
            } else {
                // pending状态
                /**
                 * 当then被多次调用同时还携带异步操作的时候,就需要把成功和失败的函数存储起来
                 * 在resolve和reject的时候循环调用
                 */
                this.successCallback.push(() => {
                    setTimeout(() => {
                        try {
                            let result = successCallback(this.successVal);
                            checkPromise(promiseBack, result, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                });
                
                this.failCallback.push(() => {
                    setTimeout(() => {
                        try {
                            let result = failCallback(this.failVal);
                            checkPromise(promiseBack, result, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0)
                });
            }
        });

        return promiseBack;
    }

    /**
     * catch 是可以在then后边链式调用的并且返回一个promise, then自己也可以链式调用并且返回一个promise
     * 这里借助then直接调用,返回rejecct
     */
    catch(failCallback) {
        return this.then(undefined, failCallback);
    }

    // finally 不管对错都会给一个结果,而且后面可以链式调用then, 所以返回一个promise
    finally(finallyCallback) {
        return this.then(val => {
            return MyPromise.resolve(finallyCallback()).then(() => val);
        }, err => {
            return MyPromise.resolve(finallyCallback()).then(() => {throw err});
        })
    }
 
    // all 是需要promise.all调用的  所以这里需要写在static上
    static all(arr) {
        return new MyPromise((resolve, reject) => {
            let result = []; // 用来存放all传进来的promise或普通值;
            let index = 0; // 

            for (let i = 0; i < arr.length; i++) {
                let current = arr[i];
                
                // 这里需要判断all方中传进来的每一个值是普通值还是promise
                if (current instanceof MyPromise) {
                    /**
                     * 1、是promise就需要拿到值,并把值存进去,如果在拿值的过程中报错了就直接报错;
                     * 2、all方法的特点是全部返回才会返回,有一个没返回就挂掉,所以用index来计数
                     */
                    current.then(val => add(i, val), err => reject(err));
                } else {
                    add(i, current);
                }
            }

            /**
             * 一个辅助函数,因为被多次运用到,抽离出来
             * 用来存放all方法中传进来的值
             */
            function add (key, value) {
                result[key] = value;
                index++; // 没存一个index就++

                if (arr.length === index) {
                    // 如果都返回了,就resolve出去
                    resolve(result);
                }
            }

        })
    }

    // resolve 是需要promise.resolve调用的  所以这里需要写在static上
    static resolve(val) {
        /**
         * resolve就是返回一个成功的promise,所以需要判断val是不是一个promise
         * 是 直接return
         * 否 创建一个promise并且把值resolve出去
         */
        if (val instanceof MyPromise) {
            return val;
        }

        return new MyPromise((resolve, reject) => resolve(val));
    }
}


/**
 * 1.一个辅助函数,有来判断then中返回的是不是一个promise,如果是就调用.then方法拿到值,然后resolve出去
 * 如果不是,就直接resolve出去
 * 2.判断是否出现了promise循环调用的情况,如果是就报错循环调用了
 */
function checkPromise(promise, result, resolve, reject) {
    // 判断回调的返回是不是和当前then的返回是一样的promise,如果是就是循环调用了
    if (promise === result) {
        return reject(new TypeError('promise 被循环调用了'));
    }
    // 判断回调的返回是不是一个promise,如果是就then到它的值然后传出去
    if (result instanceof MyPromise) {
        result.then(resolve, reject);
    } else {
        resolve(result);
    }
}