Promise面试手写代码大全

111 阅读4分钟

手写简易版Promise-麻雀虽小五脏俱全

要实现一个PromiseA+规范的代码量是很大的,而且实现的过程中有大量的边界条件是不容易理解的。在一场面试中很难会要求面试者手写一个符合A+规范的Promise,但是让大家简单实现一下还是可能出现的。

下面我和大家一起实现一个简单的Promise,但是他也是按照A+规范的思路来的,只不过省略了一些特殊的处理。 我们要达到的目的就是可以完成异步的功能,支持简单的链式调用。

  1. 使用class的形式实现Promise,并声明需要使用的变量和方法
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
    constructor(fn) {
        this.states = PENDING;
        this.value = null;
        this.reason = null;
    }
    
    resolve(value) {
    
    }
    reject(reason) {
    
    }
    then(onFulfilled,onRejected) {
    
    }
    
}
  1. 根据promise的状态流转机制实现对应的状态变化

promise.png

class MyPromise {
    constructor(fn) {
        this.states = PENDING;
        this.value = null;
        this.reason = null;
    }
    
    resolve(value) {
        if(this.states === PENDING) { //new
            this.value = value; 
            this.states = FULFILLED; 
        }
    
    }
    reject(reason) {
        if(this.states === PENDING) { //new
            this.reason = reason;
            this.states = REJECTED;
        }
    }
}

  1. Promise构建时传入一个函数fn,他接受两个参数resovle和reject并立即执行,执行过程中出错调用reject
class MyPromise {
    constructor(fn) {
        this.states = PENDING;
        this.value = null;
        this.reason = null;
        try {
            fn(this.resolve.bind(this),this.reject.bind(this));
        } catch (err) {
            this.reject(error);
        } 
    }
}
  1. then方法的实现,他接受onFulfilled,onRejected可选参数,并在promise状态变化时异步调用相应任务
class MyPromise {
    constructor(fn) {
        this.states = PENDING;
        this.value = null;
        this.reason = null;
        this.fulfilledCallbacks = [];//new 存放pending状态下获得的then方法回调onFulfilled
        this.rejectedCallbacks = [];//new 存放pending状态下获得的then方法回调onRejected
        
    }
    
   
    then(onFulfilled,onRejected) {
    
        //可选参数,处理传入不是函数情况,或者不传入
        const fulfilledFn = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
        const rejectedFn = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason};
        
        //then方法返回一个新的Promise
        
        //回调方法报错需要reject,调用需要异步,使用queueMicrotask模拟微任务调用
        switch (this.states) {
            case PENDING: {
                const newPromise = new MyPromise((resolve,reject) => {
                    //状态pending暂时不执行注册到队列
                    this.fulfilledCallbacks.push(
                        () => {
                            queueMicrotask(() => { //异步调用的回调
                                try { //被trycatch包裹检测报错
                                    let x = fulfilledFn(this.value); //执行回调
                                    resolve(x);
                                } catch(rea) {
                                    reject(rea);
                                } 
                            })
                        }
                        
                    )

                    this.rejectedCallbacks.push(
                        () => {
                            queueMicrotask(() => { //异步调用的回调
                                try { //被trycatch包裹检测报错
                                    let x = rejectedFn(this.value); //执行回调
                                    resolve(x);
                                } catch(rea) {
                                    reject(rea);
                                } 
                            })
                        }
                    )

                })

                return newPromise;
                
            }
            case FULFILLED: {
                const newPromise = new MyPromise((resolve,reject) => {
                
                    queueMicrotask(() => { //异步调用的回调
                        try { //被trycatch包裹检测报错
                            let x = fulfilledFn(this.value); //执行回调
                             resolve(x);
                        } catch(rea) {
                            reject(rea);
                        } 
                    })
                 })    
                 return newPromise;
            } 
            case REJECTED: {
                const newPromise = new MyPromise((resolve,reject) => {
                    queueMicrotask(() => { //异步调用的回调
                        try { //被trycatch包裹检测报错
                            let x = rejectedFn(this.reason); //执行回调
                            resolve(x);
                        } catch(rea) {
                            reject(rea);
                        } 
                    })
                })
                return newPromise;
                
            }
        }
    }
    
}

  1. Promise状态改变时,调用提取注册的回调。或者说resolve/reject方法调用时回调then方法注册的函数,可以直接在resolve/reject的代码里面写循环调用注册在callbacks里面的方法,在这里我用一个set/get更语义化的实现一下
class MyPromise {
    constructor(fn) {
        this._states = PENDING;
        this.value = null;
        this.reason = null;
    }
    
    set states(newStates) {
        
         this._states = newStates;
         
         if(newStates === FULFILLED) {
             this.fulfilledCallbacks.forEach(callback => callback(this.value));
         }
         
         if(newStates === REJECTED) {
             this.rejectedCallbacks.forEach(callback => callback(this.reason));
         }
        
    }
    
    get states() {
        return this._states;
    }
    
}

  1. 最复杂的一步,实现对then方法onFulfilled/onRejected回调返回值的处理,其实上面我们已经then方法已经返回了一个新的Promise来支持链式调用,但是规范里面为了处理各种不同情况,专门规定了一个resolvePromise来处理返回值。

我们简单的了解一下resolvePromise吧,参数为newPromise,返回值x,resolve,reject。下面实现一个最简单resolvePromise。


class MyPromise { 
    resolvePromise(newPro,x,resolve,reject) {
        if(newPro === x) {
            return reject( new TypeError('xxx'));//返回值和then方法生成的promise相等会产生循环依赖
        }
        
        if( x instanceof MyPromise) {
            //newPromise状态要和x保持一致
            x.then((y) => {
                this.resolvePromise(newPro,y,resolve,reject);
            },reject)
        }
        else if(typeof x === 'object' || typeof x === 'function') {
            //这里比较复杂主要是处理回调方法返回一个thenable类型怎么办,规范规定的很细致。简版里面我们不做实现了,有需要可以查看promiseA+规范。我们简单的返回resolve
            resolve(x);
        
        }
        else {
            //其他基础类型的值直接resolve
            resolve(x);
            
        }
    
    
    }
}

上面就是以一个A+规范的思路实现的简版promise。

下面我们分别实现一下Promise身上的静态方法,这是面试常考题比较简单。希望大家都能掌握

Promise.resolve / Promise.reject


class MyPromsie {
    static resolve(value) {
        if(value instanceof MyPromise) {
            return value;
        }
        return new MyPromise((resolve) => {
            resolve(value);
        });
    }
    
    static reject(reason) {
        return new MyPromise((resolve,reject) => {
            reject(reason);
        });
    }
}

Promise.race / Promise.any

class MyPromsie {
    //返回数组里面第一个成功或者失败
    static race(promiseList) {
    
        return new Promise((resolve,reject) => {
            const len = promiseList.length;
            if(len === 0) {
                return resolve();
            }
            
            for(let i = 0; i < len; i++) {
                MyPromsie.resolve(promiseList[i]).then((value) => {
                    return resolve(value);
                },(reason) => {
                    return reject(value);
                })
            }
        })
        
    }
    //返回第一个成功或者全部失败
    static any(promiseList) {
        let resList = [];
        return new Promise((resolve,reject) => {
            const len = promiseList.length;
            let num = 0;
            if(len === 0) {
                return reject(resList);
            }
            
            for(let i = 0; i < len; i++) {
                MyPromsie.resolve(promiseList[i]).then((value) => {
                    return resolve(value);
                },(reason) => {
                    resList[i] = reason;
                    num++;
                    if(num === len) {
                        return reject(resList);
                    }
                })
            }
        })
    }
}

Promise.all / Promise.allSettled

class MyPromsie {
    //返回数组里面全部成功或者一个失败
    static all(promiseList) {
        return new Promise((resolve,reject) => {
            const len = promiseList.length;
            let resList = [];
            let num = 0;
            if(len === 0) {
                return resolve();
            }
            
            for(let i = 0; i < len; i++) {
                MyPromsie.resolve(promiseList[i]).then((value) => {
                    resList[i] = value;
                    num++;
                    if(num === len) {
                        return resolve(resList);
                    }
                    
                },(reason) => {
                    return reject(value);
                })
            }
        })
        
    }
    //返回全部成功或者失败
    static allSettled(promiseList) {
        return new Promise((resolve,reject) => {
            let resList = [];
            const len = promiseList.length;
            let num = 0;
            if(len === 0) {
                return reject(resList);
            }
            
            for(let i = 0; i < len; i++) {
                MyPromsie.resolve(promiseList[i]).then((value) => {
                    resList[i] = value;
                    num++;
                    if(num === len) {
                        return resolve(resList);
                    }
                },(reason) => {
                    resList[i] = reason;
                    num++;
                    if(num === len) {
                        return resolve(resList);
                    }
                })
            }
        })
    }
}