likes
comments
collection
share

面试官:能说下promise实现异步的原理吗?

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

「这是我参与2022首次更文挑战的第25天,活动详情查看:2022首次更文挑战

前言

面试官:能说下promise实现异步的原理吗?

我:...应该是通过状态管理来实现的吧,其他我记不清了。。。

面试官:我手机就剩下95%的电了,今天就先到这吧。

当面试官问到promise的实现原理时,仅仅回答出promise的三种状态,以及通过状态管理来实现异步是远远不够的。 面试官想听到的是你对promise原理的深入了解,说出具体实现细节,比如你说到状态管理,那么状态是如何流转的?then的链式调用是如何实现的?等等。

所以要轻松应对面试官的夺命追问,就需要深入研究其原理,手写promise函数可以算得上是一条捷径了。

闲言碎语不要讲,直接上代码。

基础promise

// 声明状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
    constructor(executor) {
        // executor执行器,进入会立即执行
        executor(this.resolve, this.reject)
    }
    // 初始状态
    state = PENDING;
    // 存储异步回调
    fulfilledCallBack = null;
    rejectedCallBack = null;

    // 成功之后的值
    value = null;
    // 失败的原因
    reason = null;

    // 成功回调
    resolve = (value) => {
        console.log('execute resolve function');
        if(this.state === PENDING) {
            this.state = FULFILLED;
            this.value = value;
            // 是否有回调可执行
            this.fulfilledCallBack && this.fulfilledCallBack(value);
        }
    }
    // 拒绝回调
    reject = (reason) => {
        if(this.state === PENDING) {
            this.state = REJECTED;
            this.reason = reason;
            this.rejectedCallBack && this.rejectedCallBack(reason);
        }
    }
    then(onFulfilled, onRejected) {
        if(this.state === FULFILLED) {
            onFulfilled(this.value);
        }else if(this.state === REJECTED) {
            onRejected(this.reason);
        }else if(this.state === PENDING) {
            // 存储回调
            this.fulfilledCallBack = onFulfilled;
            this.rejectedCallBack = onRejected;
        }
    }
}

这部分很好理解,回想下我们平时使用Promise的方式:

new Promise((resolve, reject) => {resolve();...})
.then(successCB, failedCB)

所以constructor会需要接收一个函数,参数就是resolve和reject回调。

我们来测试下效果:

new MyPromise((resolve, reject) => {
    console.log('in promise', new Date().getTime());
    setTimeout(() => {
        resolve('3s 之后执行结果');
    }, 3000)
}).then(res => console.log(res, new Date().getTime()));

结果: 面试官:能说下promise实现异步的原理吗?

根据打印结果,我进一步理解到 Promise 是在 异步方法(这里是settimeout模拟的) 执行完之后才调用的resolve方法,这不就是我们在ES6之前常用的回调吗?

多次执行then方法

我们知道Promise后面可以跟多个then方法,为此,我们需要把多个then回调用 队列(先进先出) 的思想存储起来,当resolve回调触发之后,依次从 队列 里面取出回调并执行。

注意javascript里面并没有队列这种数据结构,我们可以通过数组的方法模拟实现。

// 状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
    constructor(executor) {
        // executor执行器,进入会立即执行
        executor(this.resolve, this.reject)
    }
    // 初始状态
    state = PENDING;
    // 存储异步回调
    fulfilledCallBacks = [];
    rejectedCallBacks = [];

    // 成功之后的值
    value = null;
    // 失败的原因
    reason = null;

    // 成功回调
    resolve = (value) => {
        if(this.state === PENDING) {
            this.state = FULFILLED;
            this.value = value;
            // 是否有回调可执行
            while(this.fulfilledCallBacks.length) this.fulfilledCallBacks.shift()(value);
        }
    }
    // 拒绝回调
    reject = (reason) => {
        if(this.state === PENDING) {
            this.state = REJECTED;
            this.reason = reason;
            while(this.rejectedCallBacks.length) this.rejectedCallBacks.shift()(reason); 
        }
    }
    then(onFulfilled, onRejected) {
        console.log("this.state", this.state);
        if(this.state === FULFILLED) {
            onFulfilled(this.value);
        }else if(this.state === REJECTED) {
            onRejected(this.reason);
        }else if(this.state === PENDING) {
            // 存储回调
            this.fulfilledCallBacks.push(onFulfilled);
            this.rejectedCallBacks.push(onRejected);
        }
    }
}

测试一下:

const promise = new MyPromise((resolve, reject) => {
    console.log("in promise", new Date().getTime());
    setTimeout(() => {
        resolve(2);
    }, 3000);
})
promise.then(res => {
    console.log('first', res);
})
promise.then(res => {
    console.log('second', res);
});
promise.then(res => {
    console.log('third', res);
});

面试官:能说下promise实现异步的原理吗?

链式调用

  • then 方法要链式调用那么就需要返回一个 Promise 对象
  • then 方法里面 return 一个返回值作为下一个 then 方法的参数,如果是 return 一个 Promise 对象,那么就需要判断它的状态

我们在原来的基础上进行修改:

// 状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
    constructor(executor) {
        // executor执行器,进入会立即执行
        executor(this.resolve, this.reject);
    }
    // 初始状态
    state = PENDING;
    // 存储异步回调
    fulfilledCallBacks = [];
    rejectedCallBacks = [];

    // 成功之后的值
    value = null;
    // 失败的原因
    reason = null;

    // 成功回调
    resolve = value => {
        if (this.state === PENDING) {
            this.state = FULFILLED;
            this.value = value;
            // 是否有回调可执行
            while (this.fulfilledCallBacks.length) {
                this.fulfilledCallBacks.shift()(value);
            }
        }
    };
    // 拒绝回调
    reject = reason => {
        if (this.state === PENDING) {
            this.state = REJECTED;
            this.reason = reason;
            while (this.rejectedCallBacks.length) this.rejectedCallBacks.shift()(reason);
        }
    };
    then(onFulfilled, onRejected) {
        const promise2 = new MyPromise((resolve, reject) => {
            // 成功
            const resolveMicrotask = () => {
                // 避免循环调用
                // 这里有个问题,promise2这里其实是拿不到的,因为promise2还没有完成初始化
                // 这里需要用创建一个微任务,在微任务里面调用到的就是初始化完成的promise2。
                // 我们用 queueMicrotask 创建微任务
                queueMicrotask(() => {
                    // then执行阶段错误捕获
                    try {
                        const x = onFulfilled(this.value);
                        this.resolvePromise(x, promise2, resolve, reject);
                    } catch (err) {
                        reject(err);
                    }
                });
            };
            // 失败
            const rejectMicrotask = () => {
                queueMicrotask(() => {
                    try {
                        const x = onRejected(this.reason);
                        this.resolvePromise(x, promise2, resolve, reject);
                    } catch (err) {
                        reject(err);
                    }
                });
            };

            if (this.state === FULFILLED) resolveMicrotask();
            else if (this.state === REJECTED) rejectMicrotask();
            else if (this.state === PENDING) {
                // 存储回调
                this.fulfilledCallBacks.push(resolveMicrotask);
                this.rejectedCallBacks.push(rejectMicrotask);
            }
        });
        return promise2;
    }
    resolvePromise(x, self, resolve, reject) {
        // 不能返回自身(循环调用)
        if (x === self) {
            return reject(new TypeError("The promise and the return value are the same"));
        }
        // 如果返回一个Promise对象,调用其then方法
        if (x instanceof MyPromise) {
            x.then(resolve, reject);
        } else {
            // 直接返回X
            resolve(x);
        }
    }
}

测试一下:


const promise = new MyPromise((resolve, reject) => {
    // 目前这里只处理同步的问题
    setTimeout(() => {
        resolve(2);
    }, 1000);
});

promise
    .then(value => {
        console.log(1);
        console.log("resolve", value);
        return value * 2;
    })
    .then(value => {
        console.log(2);
        console.log("resolve", value);
        return ++value;
    })
    .then(value => {
        console.log(3);
        console.log("resolve", value);
        // return value;
    });

面试官:能说下promise实现异步的原理吗?

链式调用的核心就是每次then都返回一个promise, 将then里的回调包装成promise,,依次执行resolve方法。

完善代码

我们在代码里面添加对异常的捕获,以及添加静态resolve reject方法。

// 状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
    constructor(executor) {
        // 捕获执行器的代码错误
        try{
            // executor执行器,进入会立即执行
            executor(this.resolve, this.reject)
        } catch(err) {
            this.reject(err);
        }
    }
    // 初始状态
    state = PENDING;
    // 存储异步回调
    fulfilledCallBacks = [];
    rejectedCallBacks = [];

    // 成功之后的值
    value = null;
    // 失败的原因
    reason = null;

    // 成功回调
    resolve = (value) => {
        if(this.state === PENDING) {
            this.state = FULFILLED;
            this.value = value;
            // 是否有回调可执行
            while(this.fulfilledCallBacks.length) this.fulfilledCallBacks.shift()();
        }
    }
    // 拒绝回调
    reject = (reason) => {
        if(this.state === PENDING) {
            this.state = REJECTED;
            this.reason = reason;
            while(this.rejectedCallBacks.length) this.rejectedCallBacks.shift()(); 
        }
    }
    then(onFulfilled, onRejected) {
        // 如果不传,就使用默认函数
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};

        const promise2 = new MyPromise((resolve, reject) => {
            // 成功
            const resolveMicrotask = () => {
                queueMicrotask(() => {
                    // then执行阶段错误捕获
                    try{
                        const x = onFulfilled(this.value);
                        this.resolvePromise(x, promise2, resolve, reject);
                    }catch(err) {
                        reject(err);
                    }
                })
            }
            // 失败
            const rejectMicrotask = () => {
                queueMicrotask(() => {
                    try{
                        const x = onRejected(this.reason);
                        this.resolvePromise(x, promise2, resolve, reject);
                    }catch(err) {
                        reject(err);
                    }
                })
            }

            if(this.state === FULFILLED) resolveMicrotask();
            else if(this.state === REJECTED) rejectMicrotask();
            else if(this.state === PENDING) {
                // 存储回调
                this.fulfilledCallBacks.push(resolveMicrotask);
                this.rejectedCallBacks.push(rejectMicrotask);
            }
        })
        return promise2;
    }
    resolvePromise(x, promise, resolve, reject) {
        if(x === promise) {
            return reject(new TypeError('The promise and the return value are the same'));
        }
        // 同我们原来的判断 (x instanceof MyPromise) ,这里只是为了和PromiseA+规范保持统一
        if(typeof x === 'object' || typeof x === 'function') {
            if(x === null) {
                return resolve(x);
            }
            let then;
            try{
                then = x.then;
            }catch(err) {
                return reject(err);
            }

            if(typeof then === 'function') {
                let called = false;
                try{
                    then.call(x, y => {
                        if(called) return;
                        called = true;
                        this.resolvePromise(y, promise, resolve, reject);
                    }, r => {
                        if(called) return;
                        called = true;
                        reject(r);
                    })
                }catch(err) {
                    if(called) return;
                    reject(err);
                }
            }else{
                resolve(x);
            }
        }
        else {
            resolve(x);
        }
    }
    // 静态resolve方法
    static resolve = (value) => {
        if(value instanceof MyPromise) {
            return value;
        }
        // 常规resolve处理
        return new MyPromise((resolve, reject) => {
            resolve(value);
        })
    }
    // 静态reject方法
    static reject = (reason) => {
        return new MyPromise((resolve, reject) => {
            reject(reason);
        })
    }
}

增加resolve\reject静态方法是为了满足用例:

Promise.resolve(4) 会直接返回fulfilled状态和4. 面试官:能说下promise实现异步的原理吗? reject 同理。

Promise A+ 规范

Promise 有多种规范,除了 Promise A、promise A+ 还有 Promise/B,Promise/D。

目前我们使用的 Promise 是基于 Promise A+ 规范实现的,感兴趣的移步 Promise A+ 规范了解一下。

检验一份手写 Promise 靠不靠谱,通过 Promise A+ 规范是基本要求,这里我们可以借助 promises-aplus-tests 来检测我们的代码是否符合规范。

  1. 安装一下 npm install promises-aplus-tests -D

  2. 手写代码中加入 deferred


MyPromise {
  ......
}

MyPromise.deferred = function () {
  var result = {};
  result.promise = new MyPromise(function (resolve, reject) {
    result.resolve = resolve;
    result.reject = reject;
  });

  return result;
}
module.exports = MyPromise;

  1. 配置启动命令
{
 "name": "promise",
 "version": "1.0.0",
 "description": "my promise",
 "main": "MyPromise.js",
 "scripts": {
   "test": "promises-aplus-tests MyPromise"
 },
 "author": "ITEM",
 "license": "ISC",
 "devDependencies": {
   "promises-aplus-tests": "^2.1.2"
 }
}

面试官:能说下promise实现异步的原理吗?

最终通过所有测试用例。

总结

下回遇到面试官问Promise原理我们应该如何回答呢? 自然不能是口喷代码,一股脑把手写代码背诵一遍给面试官了。

面试就像下棋,见招拆招才是制胜的法宝。

手写只是理解实现细节的手段,具体要看面试官根据你上一个问题的回答,追问的下一个问题,掌握了实现的核心原理,就可以从容应对面试官的步步紧逼了。

下回我可能会首先这么回答:

我们知道Promise一共由三种状态: Penning、Fulfilled、Rejected, 而状态是不可逆的,Promise正是通过修改状态的方式,在合适的时机触发相应状态的回调来达到处理异步的目的。通过then的链式调用也避免了回调地域的问题。

你看,这样即回答了面试官的提问,又抛出了新的方向,引导面试官发问,一举两得!!听懂掌声!

后续面试官很可能会问:”你刚提到then链式调用,可以具体说下它是如何链式调用的吗?“

再把上面的then的链式调用实现细节描述下(吧啦吧啦。。。),基本Promise的问题你就完美收官了。

如果面试官继续深挖异步编程的话,可能还会继续转向Promise的方法 all race allSettled any这些, 还有async/await这对孪生兄弟,大家可以针对性准备下。

预告

后面会写篇文章专门介绍下Promise的这几个方法,并对比他们的异同点,敬请期待。

更文不易,希望能点个赞支持下,这将鼓励我输出更多优质文章。

文中如有错误,欢迎在评论区与我交流,谢谢!