likes
comments
collection
share

随心coding—写个promise

作者站长头像
站长
· 阅读数 15

前言

手写promise是2022年末我找实习时被问到过的问题,当时水平还差的有点远,面试后也尝试学习过,但是相当吃力,问题留存至今,今天该了结一下了。

先说一下,下面的编码我没有参考任何其他大佬的代码,意思是可能我实现的比较不规范,功能也比较有限,只是向着自己预期的效果拍脑袋去写代码,肯定不是什么A+规范级别的,但说实话我觉着这种面试题不就是看我们的一个思路与动手能力么,如果感兴趣可以看看。

三个目标是从简到繁复现的我的编码思路,有任何问题欢迎大家批评指正!

目标一、实现同步的链式调用

目标测试用例:

// 同步代码链式调用
const test3 = new myPromise((resolve) => {
    resolve(1);
})
    .then((res) => {
        console.log(res); // 输出1
        return 2 * res;
    })
    .then((res) => {
        console.log(res); // 输出2
    });

编码实现(思路写在注释里):

class myPromise {
    constructor(executor) {
        // 一个promise实例就维护两个属性:一个value表示成功或者失败的值;status不用多说,pending || fullfilled || rejected
        this.value = undefined;
        this.status = 'pending';
        // 构造函数的逻辑就是执行用户传入的executor函数,并且提供resolve和reject方法。(这些都比较基本了,照顾新人还是简单说一下
        executor(this.resolve.bind(this), this.reject.bind(this));
    }
    
    // resolve要做的事情就一个:修改value和status
    resolve(value) {
        this.value = value;
        this.status = 'fullfilled';
    }
    
    // reject与resolve同理
    reject(value) {
        this.value = value;
        this.status = 'rejected';
    }
    
    // 为支持链式调用then方法返回一个promise实例,并根据status选择执行成功或是失败的回调即可
    then(onFullfilledCallback, onRejectedCallback) {
        const promise = new myPromise((resolve, reject) => {
            if(this.status === 'fullfilled') {
                const result = onFullfilledCallback(this.value);
                // 原生Promise中then方法返回的promise实例是非pending状态的,一般value值为成功回调的返回值
                resolve(result);
            }
            if(this.status === 'rejected') {
                const result = onRejectedCallback(this.value);
                reject(result);
            }
        })
        return promise;
    }
}

目标二、支持调用链中存在异步代码

(也就是延迟resolve也能正常触发链式回调)

目标测试用例:

// 支持调用链中存在异步代码
const test = new myPromise((resolve) => {
    setTimeout(() => {
        resolve(1);
    }, 500);
})
    .then((res) => {
        console.log(res);
        return 2 * res;
    })
    .then((res) => {
        console.log(res);
    });

思路分析:

熟悉js事件循环机制的话大家肯定知道上面代码的执行顺序,其实是两个then都执行完了才会执行setTimeout,所以当执行某个promise实例的then方法时这个promise其实还是pending状态的,说白了就是then先执行,resolve后执行。

所以思路就比较清晰了,我们在then中增加对pending状态的处理,处理逻辑就是先把then方法指定的回调存储起来,然后等到resolve执行时将存储的回调执行。

编码实现:

class myPromise {
    constructor(executor) {
        this.value = undefined;
        this.status = 'pending';
        
        // 增加两个任务队列用来存储promise实例处于pending状态下then方法指定的回调
+       this.onFullfilledCallbacks = [];
+       this.onRejectedCallbacks = [];

        executor(this.resolve.bind(this), this.reject.bind(this));
    }

    resolve(value) {
        this.value = value;
        this.status = 'fullfilled';
        // resolve时触发存储的回调函数
+       while(this.onFullfilledCallbacks.length !== 0) {
+           this.onFullfilledCallbacks.shift()(this.value);
+       }
    }
    
    reject(value) {
        this.value = value;
        this.status = 'rejected';
        // 同理resolve,触发回调队列里的方法
+       while(this.onRejectedCallbacks.length !== 0) {
+           this.onRejectedCallbacks.shift()(this.value);
+       }
    }
    
    then(onFullfilledCallback, onRejectedCallback) {
        const promise = new myPromise((resolve, reject) => {
            if(this.status === 'fullfilled') {
                const result = onFullfilledCallback(this.value);
                resolve(result);
            }
            if(this.status === 'rejected') {
                const result = onRejectedCallback(this.value);
                reject(result);
            }
            // 将pending状态下遇到的回调函数存储起来
+           if(this.status === 'pending') {
+               this.onFullfilledCallbacks.push(onFullfilledCallback);
+               this.onRejectedCallbacks.push(onRejectedCallback);
+           }
        })
        return promise;
    }
}

问题分析:

此时执行测试用例会发现只输出了1,第二个then方法的逻辑并没有被执行。

原因不难分析,因为第一个then方法返回的promise状态一直都是pending从未改变,所以我们修复这个问题的思路就是让then方法返回的promise状态发生改变。

何时发生改变? 这里我们需要结合测试用例详细分析一下,new返回的实例记作p1,第一个和第二个then返回的实例分别记作p2和p3(其实p3没用,因为后面没有链式调用了)

// 支持调用链中存在异步代码
const test = new myPromise((resolve) => { // 记作p1
    setTimeout(() => {
        resolve(1);
    }, 500);
})
    .then((res) => { // p2
        console.log(res);
        return 2 * res;
    })
    .then((res) => { // p3
        console.log(res);
    });

resolve执行,从而触发了p1的任务队列的执行(通过第一个then收集的回调),按理讲p1状态变了,p2也要resolve,但是p2的状态并没有改变,所以导致p2的任务队列没有执行。

解决思路就是对then放入队列中的方法进行“增强”,就是增加修改返回的promise实例状态的逻辑

编码实现:

class myPromise {
    constructor(executor) {
        this.value = undefined;
        this.status = 'pending';

        this.onFullfilledCallbacks = [];
        this.onRejectedCallbacks = [];

        executor(this.resolve.bind(this), this.reject.bind(this));
    }

    resolve(value) {
        this.value = value;
        this.status = 'fullfilled';
        while(this.onFullfilledCallbacks.length !== 0) {
+           this.onFullfilledCallbacks.shift()(); // 这里就没必要传this.value了(放到"增强"函数中了)
        }
    }

    reject(value) {
        this.value = value;
        this.status = 'rejected';
        while(this.onRejectedCallbacks.length !== 0) {
            this.onRejectedCallbacks.shift()();
        }
    }

    then(onFullfilledCallback, onRejectedCallback) {
        const promise = new myPromise((resolve, reject) => {
            if(this.status === 'fullfilled') {
                const result = onFullfilledCallback(this.value);
                resolve(result);
            }
            if(this.status === 'rejected') {
                const result = onRejectedCallback(this.value);
                reject(result);
            }
            // 对回调函数进行“增强”
+           if(this.status === 'pending') {
+               this.onFullfilledCallbacks.push(() => {
+                   const result = onFullfilledCallback(this.value);
+                   promise.resolve(result); // 执行完回调逻辑后同时改变promise的状态
+               });
+               this.onRejectedCallbacks.push(() => {
+                   const result = onRejectedCallback(this.value);
+                   promise.reject(result);
+               });
+           }
        })
        return promise;
    }
}

控制台成功输出1、2

目标三、支持then中显示返回promise来作为then的返回值

目标测试用例:

最后正确输出this,输出第二个then中返回的promise

// 支持then中返回promise
const test2 = new myPromise((resolve) => {
    setTimeout(() => {
        resolve(1);
    }, 500);
})
    .then((res) => {
        return new myPromise((resolve) => {
            resolve(2 * res);
        })
    })
    .then((res) => {
        console.log("@@@", this);
    });

思路分析:

这可太简单了,说白了就是修改then的返回值呗,在then中所有回调函数执行后判断返回值是不是promise,如果是就直接提前返回这个promise即可

代码实现:

class myPromise {
    constructor(executor) {
        this.value = undefined;
        this.status = 'pending';

        this.onFullfilledCallbacks = [];
        this.onRejectedCallbacks = [];

        executor(this.resolve.bind(this), this.reject.bind(this));
    }

    resolve(value) {
        this.value = value;
        this.status = 'fullfilled';
        while(this.onFullfilledCallbacks.length !== 0) {
            this.onFullfilledCallbacks.shift()();
        }
    }

    reject(value) {
        this.value = value;
        this.status = 'rejected';
    }

    then(onFullfilledCallback, onRejectedCallback) {
        const promise = new myPromise((resolve, reject) => {
            if(this.status === 'fullfilled') {
                const result = onFullfilledCallback(this.value);
                resolve(result);
+               if(result instanceof myPromise) {
+                   return result;
+               }
            }
            if(this.status === 'rejected') {
                const result = onRejectedCallback(this.value);
                reject(result);
+               if(result instanceof myPromise) {
+                   return result;
+               }
            }
            if(this.status === 'pending') {
                this.onFullfilledCallbacks.push(() => {
                    const result = onFullfilledCallback(this.value);
                    promise.resolve(result);
+                   if(result instanceof myPromise) {
+                       return result;
+                   }
                });
                this.onRejectedCallbacks.push(() => {
                    const result = onRejectedCallback(this.value);
                    promise.reject(result);
 +                  if(result instanceof myPromise) {
 +                      return result;
 +                  }
                });
            }
        })
        return promise;
    }
}

到这里就结束了

絮叨两句

闲聊两句自己的小感受,以前的时候写代码有种习惯是一开始就思考“最佳实践”,想着这个点怎么照顾、那个点怎么照顾,多个点怎么兼顾...以至于很难开始动手。

现在来看,其实写代码都是走一步看一步,不曾动手何谈优化捏。先把乞丐版的写出来,慢慢去完善就好了。