前言
最近打蛋在面试某场面试中,面试官问了一道让打蛋 <math xmlns="http://www.w3.org/1998/Math/MathML"> 措手不及 \color{red}措手不及 </math>措手不及(搬砖太久脑生锈了😁)的问题,什么是Promise❓,说说你的理解
在每个前端仔日常开发中,一定逃不过Promise,比如网络请求、文件读取 ,Promise的存在也解决了 <math xmlns="http://www.w3.org/1998/Math/MathML"> 横向回调地狱 \color{red}横向回调地狱 </math>横向回调地狱的问题(有使用过原生ajax的同学就深有体会),回到标题,什么是Promise呢?
什么是Promise
引用MDN上的定义:Promise
是一个对象,它代表了一个异步操作的最终完成或者失败。
我们知道Promise有三个状态, pending(初始状态)、fulfilled(成功)、rejected(失败),并且状态只能从pending -> fulfilled、pending -> rejected,并且状态改变后就无法再次变更。
模拟实现Promise
按照PromiseA+的规范,我们知道Promise有几个基础api,then(成功)、catch(失败)、resolve、reject,话不多不多、直接上代码:
这里有几个注意的点:
- 当Promise状态改变为fulfilled(成功)、rejected(失败)后抛出的值仍是Promise,仍需要进行递归处理
- 当Promise状态仍未pending时,需要在then里面将成功失败的回调推进各自的队列,等待对应状态变更执行
PromiseA+.js
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// 新建 MyPromise 类
class MyPromise {
constructor(executor){
// 储存状态的变量,初始值是 pending
this.status = PENDING;
// 成功之后的值
this.value = null;
// 失败之后的原因
this.eason = null;
// 存储成功回调函数
this.onFulfilledCallbacks = [];
// 存储失败回调函数
this.onRejectedCallbacks = [];
const resolve = value => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
// resolve里面将所有成功的回调拿出来执行
while (this.onFulfilledCallbacks.length) {
// Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
this.onFulfilledCallbacks.shift()(value)
}h
}
}
// 更改失败后的状态
const reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// resolve里面将所有失败的回调拿出来执行
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(reason)
}
}
}
// 并传入resolve和reject方法
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
const realOnRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};
//判断一下当前js执行环境
const microTask = typeof window !== 'undefined' ? queueMicrotask : process.nextTick;
const innerPromise = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
microTask(() => {
try {
// 获取成功回调函数的执行结果
const nextPromise = realOnFulfilled(this.value);
// 传入 resolvePromise 集中处理 nextPromise有可能仍是Promise
resolvePromise(innerPromise, nextPromise, resolve, reject);
} catch (error) {
reject(error)
}
})
}
const rejectedMicrotask = () => {
microTask(() => {
try {
// 调用失败回调,并且把原因返回
const nextPromise = realOnRejected(this.reason);
// 传入 resolvePromise 集中处理 nextPromise有可能仍是Promise
resolvePromise(innerPromise, nextPromise, resolve, reject);
} catch (error) {
reject(error)
}
})
}
// 判断状态
if (this.status === FULFILLED) {
fulfilledMicrotask()
} else if (this.status === REJECTED) {
rejectedMicrotask()
} else if (this.status === PENDING) {
// 等待
// 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
// 等到执行成功失败函数的时候再传递
this.onFulfilledCallbacks.push(fulfilledMicrotask);
this.onRejectedCallbacks.push(rejectedMicrotask);
}
})
return innerPromise;
}
// resolve 静态方法
static resolve (parameter) {
// 如果传入 MyPromise 就直接返回
if (parameter instanceof MyPromise) {
return parameter;
}
// 转成常规方式
return new MyPromise(resolve => {
resolve(parameter);
});
}
// reject 静态方法
static reject (reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
function resolvePromise(innerPromise, nextPromise, resolve, reject) {
// 如果相等了,说明return的是自己,抛出类型错误并返回
if (innerPromise === nextPromise) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
// 判断nextPromise是不是 MyPromise 实例对象
if(nextPromise instanceof MyPromise) {
// 执行 nextPromise,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
nextPromise.then(resolve, reject)
} else{
// 普通值
resolve(nextPromise)
}
}
那么Promise.all、race如何实现呢
我们知道Promise的all、race这两个api,传入的参数必须是一个数组,但是all是并行发送统一返回、race是看哪个promise状态率先改变,以下是实现方式:
两者的试下思路比较的简单,不同点在于all是并发但需要等到所有的子Promise成功后才返回,但是race是任一子Promise状态改变后则立即返回。all比较适合用来并行发送一些无依赖关系的异步操作、race适用于从多个异步操作获取一个数据源(可以最快拿到)。
PromiseA+.js
MyPromise.prototype.all = (promises) => {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
reject('Type Error');
}
let result = [],
count = 0
if (promises.length === 0) resolve([]);
//如果数组中有promise reject 则直接抛出异常
promises.forEach(promise => {
Promise.resolve(promise).then((data) => {
result.push(data)
++count === promise.length && resolve(result)
}).catch((error) => reject(error))
});
})
}
MyPromise.prototype.race = (promises) => {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
reject('Type Error');
}
if (promises.length === 0) resolve([]);
promises.forEach(promise => {
Promise.resolve(promise).then((data) => {
resolve(data)
}).catch((error) => reject(error))
});
})
}
结语
Promise的使用在前端开发者频率非常高,它的存在首先避免了回调地狱,在单线程的js中为开发提供了异步操作的有一种途径,内置丰富的api也让我们可以对业务场景进行封装使用,By the way, vue中的nextTick的底层实现也是Promise,只不过vue3中废弃了vue中的降级处理,不再兼容setimeout、MutationObserver这些降级处理(这也是利用了js的事件循环机制😁,下期再讲)