咱,手搓个Promise的mini版复制品玩玩?(老师说这样子就很像工作后搞开发的感觉捏)
目录
一、手搓计划
二、开整
定义个状态对象
定义一个叫MyPromise的class
定义函数
创建一个变量,用来存储Promise的结果
创建一个变量来记录Promise的状态
私有的resolve() 用来存储成功的数据,私有的reject() 用来存储拒绝的数据
禁止值被重复修改
添加一个用来读取数据的then方法
三、问题出现了!出大事,读不到异步的数据!
分析
解决办法
代码实现
四、继续开整
把孩子加入微任务队列里
让MyPromise可以反复读取数据
实现链式调用
一、手搓计划
首先是,定义类的思路:
1、先把功能都分析清楚了,在动手
2、写一点想一点,走一步看一步
那!让我们来盘一盘Promise都需要实现些什么叭:
1、能根据成功失败返回不同的值,且方法是私有的
2、有状态 (pending ; fulfilled ; rejected )
3、要克隆方法then、catch、finally
4、状态只能更改一次
然后!咱们捋一下初步的思路:
- 定义个状态对象
- 定义一个叫MyPromise的class
- 创建一个变量,用来存储Promise的结果
- 创建一个变量来记录Promise的状态
- 定义个函数,接受一个 执行器(executor) 作为参数
- 定义resolve方法、reject方法,把这两个实例方法 作为参数传给了执行器executor
- 禁止值被重复修改
- 添加一个用来读取数据的then方法
- 实现异步取值
二、开整
定义个状态对象
const PROMISE_STATE = {
PENDING:0,
FULFILLED:1,
REJECTED:2
}
定义一个叫MyPromise的class
class MyPromise{ }
定义函数
(执行器 => 形参)
定义一个resolve方法、reject方法,把这两个实例方法 作为参数传给了执行器executor
"#" —— 把这两个变成私有方法,只有在内部可以访问,但是传出来是没有问题的,但是无法通过mp.resolve()访问到。
用bind()锁死,使this一直指向实例对象
class MyPromise{
constructor(executor){
executor(this.#resolve.bind(this),this.#reject.bind(this) ) }
}
创建一个变量,用来存储Promise的结果
class MyPromise{
#result
constructor(executor){
executor(this.#resolve.bind(this),this.#reject.bind(this) )
}
}
创建一个变量来记录Promise的状态
class MyPromise{
#result
#state = PROMISE_STATE.PENDING
constructor(executor){
executor(this.#resolve.bind(this),this.#reject.bind(this) )
}
}
私有的resolve() 用来存储成功的数据,私有的reject() 用来存储拒绝的数据
class MyPromise{
#result
#state = PROMISE_STATE.PENDING
constructor(executor){
executor(this.#resolve.bind(this),this.#reject.bind(this) )
}
#resolve(value){ }
#reject(reason){ }
}
禁止值被重复修改
class MyPromise{
#result
#state = PROMISE_STATE.PENDING
constructor(executor){
executor(this.#resolve.bind(this),this.#reject.bind(this) )
}
#resolve(value){
//如果state不等于0,说明值已经被修改 函数直接返回
if(this.#state !== PROMISE_STATE.PENDING) return
this.#result = value
this.#state = PROMISE_STATE.FULFILLED //数据填充成功
}
#reject(reason){ }
}
添加一个用来读取数据的then方法
class MyPromise{
#result
#state = PROMISE_STATE.PENDING
constructor(executor){
executor(this.#resolve.bind(this),this.#reject.bind(this) )
}
#resolve(value){
//如果state不等于0,说明值已经被修改 函数直接返回
if(this.#state !== PROMISE_STATE.PENDING) return
this.#result = value
this.#state = PROMISE_STATE.FULFILLED //数据填充成功
}
#reject(reason){ }
then(onFulfilled,onRejected){
if(this.#state === PROMISE_STATE.FULFILLED){
onFulfilled(this.#result)
}
}
}
三、问题出现了!出大事,读不到异步的数据!
此时我们如果调用立即可以传输数据的result时:
const mp = new MyPromise( (resolve,reject) => {
resolve("勺子最酷酷")
resolve("果叽最酷酷")
})
//console.log(mp);
mp.then( (result) => {
console.log("读取数据",result);
})
//输出:读取数据 勺子最酷酷
这时候,貌似一切正常,风平浪静,静如处子,子子孙孙,孙孙子子。。。
但是!当我们用setTimeout模拟一下异步延时传输数据的情况:
const mp = new MyPromise( (resolve,reject) => {
setTimeout( () => {
resolve("勺子最酷酷")
resolve("果叽最酷酷")
},0)
})
//console.log(mp);
mp.then( (result) => {
console.log("读取数据",result);
})
//没有输出了!
此时,没有输出了!也就是说,咱没有实现异步时读取数据!!
分析
目前来说,then只能读取已经存储进Promise的数据,而不能读取异步存储的数据,读数据实际上就是调onFulfilled回调函数。
目前可以调回调函数的地方:
1、then里面可以通过onFulfilled回调函数返回数据
2、#resolve
当resolve执行时,说明数据已经进来了,需要调用then的回调函数onFulfilled,而onFulfilled是then的参数,resolve看不到!!!
解决办法
我们在MyPromise里面添加了一个私有属性:callback,用来存储回调函数,当我们调用then读取数据的时候,我们做了一个判断:
当状态为pending时 ———— 表示数据还没有被传进MyPromise存储
此时我们就把onFulfilled函数赋值为我们对象的callback属性的值
this.#callback = inFulfilled
当数据进入MyPromise时,#resolve就会执行了
然后就会把value赋值给result,把state修改为fulfilled
this.#result = value
this.#state = PROMISE_STATE.FULFILLED
然后就会去调this.#callback,也就是刚才我们then传过来的那个函数onFulfilled
然后就把result传给了我们的回调函数
this.#callback(this.#result)
然后我们就能在mp.then 的时候,返回异步的值了
代码实现
const PROMISE_STATE = {
PENDING:0,
FULFILLED:1,
REJECTED:2
}
class MyPromise{
#result
#state = PROMISE_STATE.PENDING
//创建一个变量来存储回调函数
#callback
constructor(executor){
executor(this.#resolve.bind(this),this.#reject.bind(this) )
}
#resolve(value){
if(this.#state !== PROMISE_STATE.PENDING) return
this.#result = value
this.#state = PROMISE_STATE.FULFILLED //数据填充成功
//这样就能访问到回调函数了,把result作为参数传进去
//这样当我们调用resolve的时候,这个callback也就执行了,然后this.result的这个参数就传给了callback
this.#callback && this.#callback(this.#result) //做一个判断,如果callback有,咱才调用
}
#reject(reason){ }
then(onFulfilled, onRejected){
if(this.#state === PROMISE_STATE.PENDING){
//进入判断,说明数据还没有进入Promise,将回调函数设置为callback的值
this.#callback = onFulfilled
}
else if(this.#state === PROMISE_STATE.FULFILLED){
onFulfilled(this.#result)
}
}
}
const mp = new MyPromise( (resolve,reject) => {
setTimeout( () => {
resolve("勺子最酷酷")
},520)
})
mp.then( (result) => {
console.log("读取数据",result);
})
这个bug目前是搞定了~这里我就一坨的写出来了,添加的地方都用注释标出来惹,这就是目前写到的完整版嗷
四、继续开整
在继续搓之前,咱们盘一盘第二阶段的思路。
- 首先,人家原版Promise里的实例函数都是在微任务队列(VIP通道)排队的捏,咱的娃都是被直接调用的,没有给他们快速通行证(游乐场里VIP通道通行的票)
- 然后,原版Promise是可以反复读取数据的,但咱不行
- 再然后,原版Promose可以链式调用,可以连续then,这咱也不行
把孩子加入微任务队列里
众所周知,普通游客想要进VIP快速通道,需要用queueMicrotask()方法捏
//在#resolve里哈,我就写个局部的更改
queueMicrotask( () => {//把孩子加到微任务队列里
this.#callback && this.#callback(this.#result)
})
//在then里哈,我也是就写个局部的更改
then(onFulfilled, onRejected){//把孩子加到微任务队列里
if(this.#state === PROMISE_STATE.PENDING){
this.#callback = onFulfilled
}
}
让MyPromise可以反复读取数据
咱先看看原汁原味的Promise怎么搞的哈
const p = Promise.resolve("勺子美滋滋")
p.then(r => console.log("第一次读",r))
p.then(r => console.log("第二次读",r))
/*
输出:
第一次读 勺子美滋滋
第二次读 勺子美滋滋
*/
但是咱的,就不能重复读取
const mp = new MyPromise( (resolve,reject) => {
setTimeout( () => {
resolve("勺子最酷酷")
},520)
})
mp.then( (result) => {
console.log("读取数据第一次",result);
})
mp.then( (result) => {
console.log("读取数据第二次",result);
})
//返回:读取数据第二次 勺子最酷酷
第二次给callback的赋值,把第一次的覆盖了, 所以只能显示第二次的
解决方法:
由于我们的回调函数可能有多个,所以我们使用数组来存储回调函数
//局部战场哈
#callback = []
//在resolve里
queueMicrotask( () => {
//this.#callback && this.#callback(this.#result) 没用了,残忍注掉
//调用callback中的所有函数,先遍历,然后调函数
this.#callback.forEach(cb => {
cb()
})
})
//在then里
if(this.#state === PROMISE_STATE.PENDING){
//this.#callback = onFulfilled - 因为callback变数组了,这条也没用,残忍咔嚓
//往数组里加一个东西,把包装后的函数直接作为参数传入
//当箭头函数调用的时候,onFulfilled也就执行了
this.#callback.push(() => {
onFulfilled(this.#result)
})
}
实现链式调用
咱就是说,能实现连续then的前提是,前一个兄弟返回的要是一个新的Promise,才能再then,但咱现在的返回值它不是捏
let result = mp.then( (result) => {})
console.log(result);
// 返回:undefined
解决方法:
我们需要让then返回的是一个Promise,所以对then就有了以下的更改
then(onFulfilled, onRejected){
return new MyPromise((resole,reject) => {
if(this.#state === PROMISE_STATE.PENDING){
this.#callback.push(() => {
onFulfilled(this.#result)
})
}
else if(this.#state === PROMISE_STATE.FULFILLED){
queueMicrotask(() => {
onFulfilled(this.#result)
})
}
})
}
但是!!但是如果我们是这么使得它then后返回的是Promise的话,它state就一直都是pending,也就是说这个里也一直没有数据,所以,咱需要给这个return new MyPromise里注入数据,通过它的resolve和reject。
那么问题来了,谁将成为then返回的新MyPromise中的数据?
答:then中回调函数的返回值会成为新Promise的数据,也就是onFulfilled的返回值会成为新数据
解决方法:
把回调函数的返回值,作为resolve的参数,传给新的Promise
const PROMISE_STATE = {
PENDING:0,
FULFILLED:1,
REJECTED:2
}
class MyPromise{
#result
#state = PROMISE_STATE.PENDING
#callback = []
constructor(executor){
executor(this.#resolve.bind(this),this.#reject.bind(this) )
}
#resolve(value){
if(this.#state !== PROMISE_STATE.PENDING) return
this.#result = value
this.#state = PROMISE_STATE.FULFILLED
queueMicrotask( () => {
this.#callback.forEach(cb => {
cb()
})
})
}
#reject(reason){ }
then(onFulfilled, onRejected){
return new MyPromise((resolve,reject) => {
if(this.#state === PROMISE_STATE.PENDING){
this.#callback.push(() => {
resolve(onFulfilled(this.#result))//把回调函数的返回值,作为resolve的参数,传给新的Promise
})
}
else if(this.#state === PROMISE_STATE.FULFILLED){
queueMicrotask(() => {
resolve(onFulfilled(this.#result)) //把回调函数的返回值,作为resolve的参数,传给新的Promise
})
}
})
}
}
const mp = new MyPromise( (resolve,reject) => {
setTimeout( () => {
resolve("勺子最酷酷")
},520)
})
mp.then( (result) => {
console.log("读取数据第一次",result);
return "勺子有点困"
})
.then(r => {
console.log("读取数据第二次",r);
return "勺子想睡觉"
})
.then(r => {
console.log("读取数据第三次",r);
})
这就是目前最新版本的全部代码了,增加改进的两个地方写了注释哈。
以上就是勺子手搓Promise复制品的现阶段实现情况了,对勺子来说,还是有一定的难度的,但是通过这次学习,大大大的加强了我对Promise的理解和对js基础的学习巩固,以后有空了我还会回来继续搓的。要去睡觉惹,如果你看这篇学习日志时是晚上的话,祝你晚安哦。