面试准备—手写Promise

270 阅读3分钟

手写Promise,已经成为大厂面试99.9%必考的一道面试题了。
写代码前,首先搞明白promise的原理及用法,知道了这些,那么每一个步骤实现起来就不会摸不着头脑了。

  • Promise其实就是一个类, 构造函数,内部保存着回调队列,通过暴露resolve和reject方法触发对应回调
  • promise有三种状态值:pending(等待),fulfiled(成功),failed(失败)
  • 常用方法以下几种:then, catch, all, race, resolve, reject
  • 一旦实例了一个Promise,其就一定会执行,无法停止,Promise中,由于报错也会触发reject,所以不会中断程序
  • catch其实是then的语法糖,相当于then(null,rej),即成功回调传了空
  • 如果then中,没有传入回调,则promise的resolve或reject值会传递到下一个then或catch中

Promise 构造函数

 function myPromise(func) {
        this.status = 'pending'; // 状态
        this.value = null; // 值
        this.resolveList = []; // 成功回调
        this.rejectList = []; // 失败回调
        
        const resolve = (data) => {
            if (this.status === 'pending') {
                this.status = 'fulfiled';
                this.value = data;
                this.resolveList.forEach((resFunc) => resFunc(data));
                this.resolveList.length = 0;
            }
        };
        
        const reject = (data) => {
            if (this.status === 'pending') {
                this.status = 'failed';
                this.value = data;
                this.rejectList.forEach((resFunc) => resFunc(data));
                this.rejectList.length = 0;
            }
        };
        
        if (typeof func === 'function') {
            try {
                func(resolve, reject); // promise传入的func是马上执行的,但回调resolve和reject是异步微任务
            } catch(err) {
                reject(err);
            }
        }
    }

then()

then方法定义在原型链上,返回一个promise, pending时将回调 添加到回调队列中,同时Promise化,fulfilled或failed时直接执行,并返回promise

myPromise.prototype.then = function (resCb, rejCb) {
    if (this.status === 'pending') {
        return new myPromise((res, rej) => {
            this.resolveList.push(() => {
                handleResolve(res, rej, resCb, rejCb, this.value)
            });
            this.rejectList.push(() => {
                handleReject(res, rej, resCb, rejCb, this.value)
            })
        })
    } else if (this.status === 'fulfiled') {
        return new myPromise((res, rej) => { 
            handleResolve(res, rej, resCb, rejCb, this.value)
        })
    } else if (this.status === 'failed') {
        return new myPromise((res, rej) => {
            handleReject(res, rej, resCb, rejCb, this.value)
        })
    }
}
 function handleResolve(res, rej, resCb, rejCb, value) {
        queueMicrotask(() => {
            // 如果没有定义回调,将值传递给下一个
            try {
                const ret = typeof resCb === 'function' ? resCb(value) : value;
                if (ret instanceof myPromise) {
                    ret.then(res, rej);
                } else {
                    res(ret)
                }   
            } catch (err) {
                rej(err);
            }
        });
    }

    function handleReject(res, rej, resCb, rejCb, value) {
        queueMicrotask(() => {
            // 如果没有定义回调,将值传递给下一个
            try {
                const ret = typeof rejCb === 'function' ? rejCb(value) : value
                if (ret instanceof myPromise) {
                    ret.then(res, rej);
                } else {
                    rej(ret);
                }   
            } catch (err) {
                rej(err);
            }
        });
    }

catch()

myPromise.prototype.then(null, rej) 的语法糖

myPromise.prototype.catch = function(rej){
   return myPromise.prototype.then.call(this, null, rej)
} 

all()

传入一个数组,输出一个promise,全部resolve时,输出promise状态变为fulfiled,触发resolve 当任何一个传入的promise失败时,输出promise状态变failed,触发reject, 但并不会终止其他promise继续执行,只是其他promise的执行已经不会影响promise.all返回promise的状态了

myPromise.all = function(promiseArr){
    const len = promiseArr.length
    const result = []
    return new myPromise((resolve, reject) => {
        let index = 0
        promiseArr.forEach((item) => {
            item.then((data) => {
                result.push(data)
                index++
                if (index === len) resolve(result)
            }, (err) => {
                reject(err)
            })
        })
    })
}

race()

返回promise数组中第一个执行完毕的状态,无论是fulfiled还是failed,但不影响数组中其余promise继续执行

myPromise.race = function(promiseArr) {
    return new myPromise(function(resolve, reject){
        promiseArr.forEach(item => {
            item.then(data => {
                resolve(data)
            }, err=> {
                reject(err)
            })
        })
    }) 
}

resolve()

将现有数据转换为Promise,状态为resolved

myPromise.resolve = function(data){
    return new myPromise((resolve, reject) => {
        resolve(data)
    })
};

reject()

myPromise.reject = (data) => {
    return new myPromise((resolve, reject) => {
        reject(data);
    });
};

finally()

无论最后promise状态如何都会执行

myPromise.prototype.finally = function (callback){
    let p = this.constructor
    return this.then(
        value => p.resolve(callback()).then(() => value),
        reason => p.resolve(callback()).then(() => {throw reason})
    )
}

done()

处于回调链的末端,能捕获到任何可能出现的错误

myPromise.prototype.done = function (onFulfilled, onRejected) {
    this.then(onFulfilled, onRejected).catch(function(reason){
        setTimeout(function(){
            throw reason
        }, 0)
    })
}