面试官:能说下promise实现异步的原理吗?
「这是我参与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 是在 异步方法(这里是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);
});
链式调用
- 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;
});
链式调用的核心就是每次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.
reject
同理。
Promise A+ 规范
Promise 有多种规范,除了 Promise A、promise A+ 还有 Promise/B,Promise/D。
目前我们使用的 Promise 是基于 Promise A+ 规范实现的,感兴趣的移步 Promise A+ 规范了解一下。
检验一份手写 Promise 靠不靠谱,通过 Promise A+ 规范是基本要求,这里我们可以借助 promises-aplus-tests 来检测我们的代码是否符合规范。
-
安装一下
npm install promises-aplus-tests -D
-
手写代码中加入 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;
- 配置启动命令
{
"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一共由三种状态: Penning、Fulfilled、Rejected, 而状态是不可逆的,Promise正是通过修改状态的方式,在合适的时机触发相应状态的回调来达到处理异步的目的。通过then的链式调用也避免了回调地域的问题。
你看,这样即回答了面试官的提问,又抛出了新的方向,引导面试官发问,一举两得!!听懂掌声!
后续面试官很可能会问:”你刚提到then链式调用,可以具体说下它是如何链式调用的吗?“
再把上面的then的链式调用实现细节描述下(吧啦吧啦。。。),基本Promise的问题你就完美收官了。
如果面试官继续深挖异步编程的话,可能还会继续转向Promise的方法 all race allSettled any这些, 还有async/await这对孪生兄弟,大家可以针对性准备下。
预告
后面会写篇文章专门介绍下Promise的这几个方法,并对比他们的异同点,敬请期待。
更文不易,希望能点个赞支持下,这将鼓励我输出更多优质文章。
文中如有错误,欢迎在评论区与我交流,谢谢!
转载自:https://juejin.cn/post/7069783225635176455