「这是我参与2022首次更文挑战的第28天,活动详情查看:2022首次更文挑战」
已经了解了promiseA+规范,这就开始用JS实现promsie 的核心逻辑。在实现(模仿)之前,我们先看看正牌的Promise的基本使用。
如果想拆开地狱回调,就应该把下一层的promise作为then的解决回调的返回值,这样就能转为链式。
如此一来,看起来会舒服很多。并且只需要在最后catch一下即可。
创建实例的一般方式就是, new 一个。需要一个函数作为参数,这个函数的前两个参数是 解决和拒绝的回调。函数的主体逻辑就是期约的执行逻辑。包含何时解决/拒绝
let p = new Promise((res,rej)=>{
setTimeout(() => {
res(1)
}, 1);
if(2 > 3){
rej('')
})
then 方法的使用,任何期约实例上都可调用,then方法可调用多次, 有两个可选参数。并且可以在then返回的实例上再调用catch。
p.then(()=>{
console.log(p)
// return Promise.resolve(2)
}, ()=> {}).then((n)=>{console.log(n,1)})
p.then(()=>{
return 2
}).then((n)=>{console.log(n,2)}).catch(()=> {})
实现自己的promise
状态定义及转变
先实现状态定义,默认是pending。 然后就是执行传入的回调,这个回调函数有两个可选参数(期望是函数),这两个函数一旦调用就会改变期约的状态和得到最终结果。下面用两个私有方法将其抽离出来。
class Appoint{
/* new promise 需传入一个函数,每个实例须有自己的状态,预备一个解决结果 */
constructor(handle=(res,rej)=> {}){
this['[[PromiseState]]'] = 'pending'
this['[[PromiseResult]]'] = undefined
handle(this.#resolve, this.#reject)
}
/* 调用静态方法转变状态,改变结果 */
#resolve(val) {
this["[[PromiseState]]"] = 'fullfilled'
this["[[PromiseResult]]"] = val
}
#reject(err){
this["[[PromiseState]]"] = 'rejected'
this["[[PromiseResult]]"] = err
}
}
这样,状态定义和转变就好了。 然后执行的时候就发现一个问题, 那就是在调用resolve或者reject的时候发现,this不对,确实不对,上下文环境已经改变。我们需要处理一下。
handle(this.#resolve.bind(this), this.#reject.bind(this))
实现then方法
then方法应该在状态变化之后立即执行,then方法也有两个可选参数,分别在拒绝和兑现之后调用。 因此,我们应该在执行私有方法#resolve或#reject之后再执行对应的回调,也就是需要把回调写在改变状态的私有方法里。
然后解决几个问题:
发布订阅
then可以调用多次,需要把全部的then的参数保存起来,调用的时候依次调,注意可以有多个then但是每个then只能调用一次。这里直接for循环 然后清空数组,如果按照队列的规范应该一边出队(shift)一边执行。
微任务
解决then的回调需要在当前执行上下文栈空时执行, 而不是立即执行。
之前的实现需要我们传入一个异步逻辑才能保证其逻辑。如果我们写一个同步的代码,那么可能会立即执行then的逻辑,但是此时,then的回调还没收集。 这里使用mutationObserver来实现微任务。 也可以直接使用 ququeMicrotask
链式
解决 then的返回值应该是一个promise 并且状态之前的一样, 返回的新的promise实例的理由/值 由then的返回值决定。
如果是一个非Promise值,那么就以此作为解决值。 如果是一个promise则返回的实例的状态和值应当和其相同。
传递返回值,所以先拿到返回值,然后把它作为新期约的结果进行解决, 并且将新期约的创建作为一个当前then的回调函数 ,这样就能在当前then回调执行完之后,立即构建这个期约
return new Appoint((res,rej)=> {
let resovleFn = function(val){
let result = resolve && resolve(val)
if( result instanceof Appoint){
result.then(resolve)
}else {
res(result)
}
}
this.onResolveQueue.push( resovleFn)
let rejecteFn = function(val){
rejecte && rejecte(val)
rej(val)
}
this.onRejectQueue.push(rejecteFn)
})
至此,我们的Appoint的核心逻辑就完成了。