我觉得Promise是JS
编程中离不开的东西,因为它是来实现异步操作的最优解,而JS
的核心就是异步
# 重要的点
很多人都知道的是,Promise
是用来解决回调地狱的,但我们要知道Promise
的更重要的作用,其实是用来存储异步数据的,我们是利用它自己独特的一种存储异步数据的方式,来帮助我们进行代码的编写~
存储异步数据的方式就是它构造函数的两个参数 resolve
和 reject
resolve
存储正常返回的数据reject
存储出现异常的数据
无论是resolve
还是reject
,promise
的数据都只能存一次,存过一次之后这个值就是该promise
的值了,不能再resolve
或reject
了
处理异步任务时,难的不是取数据,也不是输出数据,而是读取数据的时间
所以Promise
里有一个静态属性 —— PromiseStatus
, 就存储了Promise
的三种状态,状态就帮我们指示了读取数据的时间
.then
方法就是读取Promise
中存储的数据,相当于为Promise
设置了回调函数(可以想象成给DOM
节点绑定事件,如果Promise
里resolve
或reject
了,就会调用这个回调函数)
- 如果
PromiseState
变为fulfilled
,则调用then
的第一个回调函数来返回数据 - 如果
PromiseState
变为rejected
,则调用then
的第二个回调函数来返回数据
# 💪手写一个Promise
直接手写一个Promise
,是理解Promise
构造的最好的方式
::需求分析(简单想一下Promise
要实现哪些功能)
- 构造函数
- 存数据,解决
this
是undefined
,只能存储一次数据 - 读数据,读取异步数据(读还没存进去的)
then
的回调函数应该放到微任务执行- 解决多个
then
调用(反复读数据),回调函数有多个,只有最后一个能读出来,其他的读不出来,导致回调函数丢失 - 实现链式调用(把
onFulfilled
的返回值赋给resolve
,然后把resolve
作为then
返回的promise
对象的值)
# 构造函数
首先我们要声明一个MyPromise
的类,这个类一定是有一个构造函数,同时,因为在我们声明一个promise
实例时,传入的构造函数是立即执行的,所以我们设置的构造函数要执行传入的函数
同时传入的两个函数resolve
和reject
是用来存数据的,但这不是我们类的方法,所以我们要在内部声明这两个方法,用来存数据👇
class MyPromise {
#result;
constructor(executor) {
executor(this.#resolve, this.#reject);
}
#resolve(value) {
console.log("value: ", value);
}
#reject(value) {}
}
const mp = new MyPromise((resolve, reject) => {
resolve("test");
});
上面代码的输出结果👇,现在能够拿到用户resolve
的值了,但是现在我们还没有存储到我们的promise
中
# 存数据
无论是resolve
还是reject
,我们最重要的就是要把数据存储起来,但其实这实现起来很简单,定义一个私有变量,用来存储数据即可😄
这里要注意的是,
resolve
时this
是undefined
,因为此时是以函数形式调用的,严格模式下this
就是undefined
,所以我们要通过this
来访问变量,就要给resolve
绑定this
是我们的myPromise
类
class MyPromise {
// 存储resolve或reject的数据
#result;
constructor(executor) {
executor(this.#resolve.bind(this), this.#reject.bind(this));
}
// resolve存数据逻辑
#resolve(value) {
this.#result = value;
console.log("result: ", this.#result);
}
// reject存数据逻辑
#reject(value) {
this.#result = value;
}
}
const mp = new MyPromise((resolve, reject) => {
resolve("test");
});
现在可以看到result
存储到数据了
但之前说过了,promise的数据只能存一次,所以我们要设置一个状态对象,通过它来判断能不能继续存
const PROMISE_STATUS = {
PENDING: 0,
FULFILLED: 1,
REJECTED: 2,
};
class MyPromise {
// 存储myPromise的状态
#status = PROMISE_STATUS.PENDING;
// ...
// ...
// resolve存数据逻辑
#resolve(value) {
if (this.#status !== PROMISE_STATUS.PENDING) {
return;
}
this.#result = value;
this.#status = PROMISE_STATUS.FULFILLED;
}
// reject存数据逻辑
#reject(value) {
if (this.#status !== PROMISE_STATUS.PENDING) {
return;
}
this.#result = value;
this.#status = PROMISE_STATUS.REJECTED;
}
}
const mp = new MyPromise((resolve, reject) => {
resolve("test");
});
# 读数据
读数据要靠then
方法,then
方法有两个参数,第一个函数用来返回resolve
的数据,第二个函数用来返回reject
的数据,所以我们要写一个then
方法用来读数据 —— 两个参数就是两个回调
class MyPromise{
// ...
then(onFulfilled, onRejected) {
if (this.#status === PROMISE_STATUS.FULFILLED) {
onFulfilled(this.#result);
}
if (this.#status === PROMISE_STATUS.REJECTED) {
onRejected(this.#result);
}
}
}
const mp = new MyPromise((resolve, reject) => {
resolve("test");
});
mp.then(
(result) => {
console.log("读到了数据: ", result);
},
(reason) => {
console.log("发生了错误: ", reason);
}
);
调用then
方法,可以看到我们可以读到result
的数据了☝️
但现在还存在一个问题,无法读取异步的数据,也就是我们如果存数据的时候是异步存的,那我们读数据的时候是读不到的,因为数据还没有存进去
所以我们应该在别的地方去执行then
传进来的回调 —— 在resolve
和reject
里执行,因为 resolve
了就证明数据存进来了,就可以执行then
的回调了
class MyPromise {
// ...
// 存储then传进来的回调
// resolve回调
#resolvedCallbacks = [];
// reject回调
#rejectedCallbacks = [];
constructor(executor) {
executor(this.#resolve.bind(this), this.#reject.bind(this));
}
// resolve存数据逻辑
#resolve(value) {
if (this.#status !== PROMISE_STATUS.PENDING) {
return;
}
this.#result = value;
this.#status = PROMISE_STATUS.FULFILLED;
this.#resolvedCallbacks &&
this.#resolvedCallbacks.forEach((cb) => {
cb(this.#result);
});
}
// reject存数据逻辑
#reject(value) {
if (this.#status !== PROMISE_STATUS.PENDING) {
return;
}
this.#result = value;
this.#status = PROMISE_STATUS.REJECTED;
this.#rejectedCallbacks &&
this.#rejectedCallbacks.forEach((cb) => {
cb(this.#result);
});
}
then(onFulfilled, onRejected) {
if (this.#status === PROMISE_STATUS.PENDING) {
onFulfilled && this.#resolvedCallbacks.push(onFulfilled);
onRejected && this.#rejectedCallbacks.push(onRejected);
}
if (this.#status === PROMISE_STATUS.FULFILLED) {
onFulfilled(this.#result);
}
if (this.#status === PROMISE_STATUS.REJECTED) {
onRejected(this.#result);
}
}
}
const mp = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("test");
}, 1000);
});
mp.then(
(result) => {
console.log("读到了数据: ", result);
},
(reason) => {
console.log("发生了错误: ", reason);
}
);
这样测试一下,发现一秒后控制台输出了 读到了数据: test 的结果~
# 微任务
对js执行机制有了解的,应该知道,promise
的回调是在微任务队列中执行的,所以我们在实现时也要放到微任务队列中,通过queueMicrotask
方法来实现
// ...
// resolve存数据逻辑
#resolve(value) {
if (this.#status !== PROMISE_STATUS.PENDING) {
return;
}
this.#result = value;
this.#status = PROMISE_STATUS.FULFILLED;
queueMicrotask(() => {
this.#resolvedCallbacks &&
this.#resolvedCallbacks.forEach((cb) => {
cb(this.#result);
});
});
}
// ...
if (this.#status === PROMISE_STATUS.FULFILLED) {
queueMicrotask(() => {
onFulfilled(this.#result);
});
}
# 链式调用
promise
最大的特点之一就是**.then
的返回值也是一个promise
对象**,该对象中的数据就是上一个promise
回调函数的返回值 —— onFulfilled
的返回值设置成下一个promise
的数据,且是resolve
的数据,不是reject
的数据 —— 下一个promise
的resolve
才能访问,reject
访问不到
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.#status === PROMISE_STATUS.PENDING) {
onFulfilled &&
this.#resolvedCallbacks.push(() => {
resolve(onFulfilled(this.#result));
});
onRejected &&
this.#rejectedCallbacks.push(() => {
reject(onFulfilled(this.#result));
});
}
if (this.#status === PROMISE_STATUS.FULFILLED) {
queueMicrotask(() => {
resolve(onFulfilled(this.#result));
});
}
if (this.#status === PROMISE_STATUS.REJECTED) {
queueMicrotask(() => {
reject(onRejected(this.#result));
});
}
});
}
const mp = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("test");
}, 1000);
});
mp.then((result) => {
console.log("1读到了数据: ", result);
return "2";
})
.then((result) => {
console.log("2读到了数据: ", result);
return "3";
})
.then((result) => {
console.log("3读到了数据: ", result);
});
console.log("test");
输出的结果☝️,可以看到实现了链式调用和微任务的配置~
# ⭐️最终版
这样我们就得到了一个手撕版本的Promise
啦~
const PROMISE_STATUS = {
PENDING: 0,
FULFILLED: 1,
REJECTED: 2,
};
class MyPromise {
// 存储resolve或reject的数据
#result;
// 存储myPromise的状态
#status = PROMISE_STATUS.PENDING;
// 存储then传进来的回调
// resolve回调
#resolvedCallbacks = [];
// reject回调
#rejectedCallbacks = [];
constructor(executor) {
executor(this.#resolve.bind(this), this.#reject.bind(this));
}
// resolve存数据逻辑
#resolve(value) {
if (this.#status !== PROMISE_STATUS.PENDING) {
return;
}
this.#result = value;
this.#status = PROMISE_STATUS.FULFILLED;
queueMicrotask(() => {
this.#resolvedCallbacks &&
this.#resolvedCallbacks.forEach((cb) => {
cb();
});
});
}
// reject存数据逻辑
#reject(value) {
if (this.#status !== PROMISE_STATUS.PENDING) {
return;
}
this.#result = value;
this.#status = PROMISE_STATUS.REJECTED;
queueMicrotask(() => {
this.#rejectedCallbacks &&
this.#rejectedCallbacks.forEach((cb) => {
cb();
});
});
}
// 读数据
// 返回一个新的MyPromise
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.#status === PROMISE_STATUS.PENDING) {
onFulfilled &&
this.#resolvedCallbacks.push(() => {
resolve(onFulfilled(this.#result));
});
onRejected &&
this.#rejectedCallbacks.push(() => {
resolve(onRejected(this.#result));
});
}
if (this.#status === PROMISE_STATUS.FULFILLED) {
queueMicrotask(() => {
resolve(onFulfilled(this.#result));
});
}
if (this.#status === PROMISE_STATUS.REJECTED) {
queueMicrotask(() => {
resolve(onRejected(this.#result));
});
}
});
}
}
const mp = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("test");
}, 1000);
});
mp.then((result) => {
console.log("1读到了数据: ", result);
return "2";
})
.then((result) => {
console.log("2读到了数据: ", result);
return "3";
})
.then((result) => {
console.log("3读到了数据: ", result);
});
console.log("test");