我觉得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"); });
Copied!
上面代码的输出结果👇,现在能够拿到用户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"); });
Copied!
现在可以看到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"); });
Copied!
# 读数据
读数据要靠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); } );
Copied!
调用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); } );
Copied!
这样测试一下,发现一秒后控制台输出了 读到了数据: 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); }); }
Copied!
# 链式调用
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");
Copied!
输出的结果☝️,可以看到实现了链式调用和微任务的配置~
# ⭐️最终版
这样我们就得到了一个手撕版本的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");
Copied!
v1.5.1