符合promiseA+规范,那它就是一个标准的promise。一起来手写一个promise吧。

611 阅读9分钟

看了袁进老师的视频,我忍不住写篇文章跟着手写一遍promise

哈喽大家好,我是你们的金樽清酒。学习前端的小伙伴呀,在工作或面试的时候都逃不过一个东西,那就是promise,将异步代码变成同步代码。在没有promise之前都是通过回调函数来完成的,有了promise之后,就解决了回调地狱的问题,变成链式调用promise,使代码更加美观。要理解promise呢,最好手写一遍,才能理解透彻,今天就跟着我回顾一下吧。(这个手写promise是看袁进老师的视频学的)

prmiseA+规范

首先,怎么的promise才算promise呢?要搞懂这个问题,我们得看promise的A+规范。诶,只要符合了promsie的A+规范的那就是真正的promsie。要A+规范只要强调了一个什么事情呢?我们来看一下A+规范文档。 promisesaplus.com.cn/

截屏2024-12-22 13.57.49.png 这A+规范说了啥了。主要是强调了promise里面的then方法的实现。如果实现符合这个规范的话。那就是一个真正的promise。也就是说promise最重要的就是这个then方法的实现。满足这个规范呢。不同的promise函数之间都能进行操作,有一个互操性。你说这牛不牛逼。好了,话不多说,开写。

promise的构造器

分析一下promise,我们在用的时候是不是new 一个promise。执行一个回调,接受两个参数,resolve和reject。并且promsie有三个状态,fulfilled,pending,和rejected。resolve和reject会改变promise的状态,并且状态改变不可逆。

let a = new promise((resolve,reject)=>{

})

那我们写的promise的构造器要达到上述的效果。

class MyPromise{
    static PENDING = 'penfing'
    static FULFILLED ='fulfilled'
    static REJRCTED = 'rejected'
    #result = undefined
    #state = MyPromise.PENDING
        constructor(excutor){
        const resolve = (result)=>{
        this.#change(result,MyPromise.FULFILLED)
        console.log(this.#result);
        }
        const reject = (reason)=>{
            this.#change(reason,MyPromise.REJRCTED)
         }
         try{
          excutor(resolve,reject)
         }catch(error){
             reject(error)
         }
       
      }
     #change(data,status){
    if(this.#state!== MyPromise.PENDING) return
    this.#result = data
    this.#state = status
    }
}

首先我们先定义几个静态属性,用staic关键词修饰的可以被类直接用。这是防止硬编码。后面的状态不直接写死,由于用的地方比较多,如果后续要修改,则会改动很多地方。这是一个程序员应该想到的问题。

用#修饰的是私有属性,不会向外界暴露。change方法是我们抽离出来的函数。因为resolve和reject有很多相似的地方,所以封装一个change去调用。遵循dry原则,never repeat yourself。只有提高复用率,才能提高你的编程水平,写出好代码。

promise的then的返回值。

我们先看一下then的使用,它里面有两个回调参数,一个成功的回调,一个失败的回调。并且then能完成链式调用,说明它会返回一个promsie,而且我们能多次调用then方法,那我来写一下then方法吧。

先把上面的代码抄下来,现在我们要实现一个then方法们也就是A+规范中最重要的东西。

class MyPromise{
    static PENDING = 'penfing'
    static FULFILLED ='fulfilled'
    static REJRCTED = 'rejected'
    #result = undefined
    #state = MyPromise.PENDING
    #handler = []
        constructor(excutor){
        const resolve = (result)=>{
        this.#change(result,MyPromise.FULFILLED)
        console.log(this.#result);
        }
        const reject = (reason)=>{
            this.#change(reason,MyPromise.REJRCTED)
         }
         try{
          excutor(resolve,reject)
         }catch(error){
             reject(error)
         }
       
      }
     #change(data,status){
    if(this.#state!== MyPromise.PENDING) return
    this.#result = data
    this.#state = status
    }
    
    #run(){
        if(this.#state === MyPromise.PENDING) return 
        while(this.#handler.length>0){
          const { onFULfilled, onRejected, resolve, reject } = this.#handler.shift()
            if(this.#state === MyPromise.FULFILLED){
            //处于完成状态
            if(typeof onFULfilled === 'function'){
                onFULfilled()
            }
           }else{
            if(typeof onFULfilled === 'function'){
                onRejected()
            }
           }
        }
    }
    
    then(onFULfilled,onRejected){
        return new MyPromise((resolve,reject)=>{
      //将要执行的函数收集起来,当状态发生变更的时候调用。
    this.#handler.push({ onFULfilled, onRejected, resolve, reject});
    this.#run()
    })
   }
}

为了解决多次调用then的问题,我们需要定义一个私有变量handler为一个数组。在调用then的时候把成功或失败的回调添加进数组,在状态发生变化的时候调用。我们用一个run方法来执行。

then中的promise的resolve和reject的执行时机

我怎么知道什么时候调用resolve,什么时候调用reject呢? 分为三种情况

第一种情况,在then中参数不是一个函数。这个时候就要透传,根据前面的状态判断是调用惹resolve还是reject。然后把前一个promise的值传入。

第二种情况 传入的是函数,且返回具体的值。用try,catch。没报错就调用resolve(),报错则调用reject。

第三种情况 传入的是函数,返回promise。则调用then方法。

我们复制一份上面的代码,在run函数里面进行判断。

class MyPromise{
    static PENDING = 'penfing'
    static FULFILLED ='fulfilled'
    static REJRCTED = 'rejected'
    #result = undefined
    #state = MyPromise.PENDING
    #handler = []
        constructor(excutor){
        const resolve = (result)=>{
        this.#change(result,MyPromise.FULFILLED)
        console.log(this.#result);
        }
        const reject = (reason)=>{
            this.#change(reason,MyPromise.REJRCTED)
         }
         try{
          excutor(resolve,reject)
         }catch(error){
             reject(error)
         }
       
      }
     #change(data,status){
    if(this.#state!== MyPromise.PENDING) return
    this.#result = data
    this.#state = status
    }
    
    #run(){
        if(this.#state === MyPromise.PENDING) return 
        while(this.#handler.length>0){
          const { onFULfilled, onRejected, resolve, reject } = this.#handler.shift()
            if(this.#state === MyPromise.FULFILLED){
            //处于完成状态
            if(typeof onFULfilled === 'function'){
                onFULfilled()
            }else{
            //回调不是函数,则进行透传
                resolve(this.#result)
            }
           }else{
            if(typeof onRejected === 'function'){
                onRejected()
            }else{
                reject(this.#result)
            }
           }
        }
    }
    
    then(onFULfilled,onRejected){
        return new MyPromise((resolve,reject)=>{
      //将要执行的函数收集起来,当状态发生变更的时候调用。
    this.#handler.push({ onFULfilled, onRejected, resolve, reject});
    this.#run()
    })
   }

可以看到我们这么写的话又有好多重复的逻辑。那我们又可以抽一个函数出来,判断什么时候调用resolve和reject。我们写一个runOne辅助函数。

class MyPromise{
    static PENDING = 'penfing'
    static FULFILLED ='fulfilled'
    static REJRCTED = 'rejected'
    #result = undefined
    #state = MyPromise.PENDING
    #handler = []
        constructor(excutor){
        const resolve = (result)=>{
        this.#change(result,MyPromise.FULFILLED)
        console.log(this.#result);
        }
        const reject = (reason)=>{
            this.#change(reason,MyPromise.REJRCTED)
         }
         try{
          excutor(resolve,reject)
         }catch(error){
             reject(error)
         }
       
      }
     #change(data,status){
    if(this.#state!== MyPromise.PENDING) return
    this.#result = data
    this.#state = status
    }
    
    #runOne(callBack, resolve, reject){
        if (typeof callBack !== 'function') {
            const settled = this.#state === MyPromise.FULFILLED ? resolve : reject
            settled(this.#result)
           } 
           else {
             try {
                const data = callBack(this.#result)
                if (this.#isPromiseLike(data)) {
                data.then(resolve, reject)
                } else {
                    resolve(data)
                  }
            } catch (error) {
                reject(error)
               }
            }
    }
    #run(){
        if(this.#state === MyPromise.PENDING) return 
        while (this.#handler.length > 0) {
            const { onFULfilled, onRejected, resolve, reject } = this.#handler.shift()
            if (this.#state === MyPromise.FULFILLED) {
                this.#runOne(onFULfilled, resolve, reject)
            } else {
                onRejected(this.#result)
                this.#runOne(onRejected, resolve, reject)
               }
            }
    }
    
    then(onFULfilled,onRejected){
        return new MyPromise((resolve,reject)=>{
      //将要执行的函数收集起来,当状态发生变更的时候调用。
    this.#handler.push({ onFULfilled, onRejected, resolve, reject});
    this.#run()
    })
   }

写到这呢,还没完,上面的代码不是有个isPromiseLike辅助函数判断是不是promise嘛。为啥不直接用data instancof MyPromise呢?这不行,这只能判断返回的是不是我们写的这个promise,而只要符合promiseA+规范的那就是promise。也就是说返回的是官方的promise和你写的promise都是promise,可以相互之间使用的,它是有互操性的。那我们来完善一下这个代码,看一下,什么条件是promiseA+规范的promise呢?复制一下上面的代码。

class MyPromise{
    static PENDING = 'penfing'
    static FULFILLED ='fulfilled'
    static REJRCTED = 'rejected'
    #result = undefined
    #state = MyPromise.PENDING
    #handler = []
        constructor(excutor){
        const resolve = (result)=>{
        this.#change(result,MyPromise.FULFILLED)
        console.log(this.#result);
        }
        const reject = (reason)=>{
            this.#change(reason,MyPromise.REJRCTED)
         }
         try{
          excutor(resolve,reject)
         }catch(error){
             reject(error)
         }
       
      }
     #change(data,status){
    if(this.#state!== MyPromise.PENDING) return
    this.#result = data
    this.#state = status
    }
    
    #isPromiseLike(value){
       if (value !== null && (typeof value === 'object' || typeof value ==='function')) {
            return typeof value.then === 'function'
        } else {
        return false
        }
    }
    #runOne(callBack, resolve, reject){
        if (typeof callBack !== 'function') {
            const settled = this.#state === MyPromise.FULFILLED ? resolve : reject
            settled(this.#result)
           } 
           else {
             try {
                const data = callBack(this.#result)
                if (this.#isPromiseLike(data)) {
                data.then(resolve, reject)
                } else {
                    resolve(data)
                  }
            } catch (error) {
                reject(error)
               }
            }
    }
    #run(){
        if(this.#state === MyPromise.PENDING) return 
        while (this.#handler.length > 0) {
            const { onFULfilled, onRejected, resolve, reject } = this.#handler.shift()
            if (this.#state === MyPromise.FULFILLED) {
                this.#runOne(onFULfilled, resolve, reject)
            } else {
                onRejected(this.#result)
                this.#runOne(onRejected, resolve, reject)
               }
            }
    }
    
    then(onFULfilled,onRejected){
        return new MyPromise((resolve,reject)=>{
      //将要执行的函数收集起来,当状态发生变更的时候调用。
    this.#handler.push({ onFULfilled, onRejected, resolve, reject});
    this.#run()
    })
   }

主要是看里面有什么then方法。

截屏2024-12-22 22.51.44.png 如图,我们在自己写的Promise里面返回官方的promise,一样返回正确的结果。这就是互操性。只要符合promiseA+规范的就是promise。只不过官方还封装了很多方法,但是,难道没有那些方法我这就不叫promise了嘛。九二爷比你多了学历你就不是程序员了嘛,显然不成立。

写到这就完了嘛,不,还少了一点点内容。微任务队列。还记得我们的then是微任务嘛。可是咱们现在是同步代码。那怎么办呢?我们需要根据环境模拟微任务,在then调用的时候使用。复制一下代码。

class MyPromise{
    static PENDING = 'penfing'
    static FULFILLED ='fulfilled'
    static REJRCTED = 'rejected'
    #result = undefined
    #state = MyPromise.PENDING
    #handler = []
        constructor(excutor){
        const resolve = (result)=>{
        this.#change(result,MyPromise.FULFILLED)
        console.log(this.#result);
        }
        const reject = (reason)=>{
            this.#change(reason,MyPromise.REJRCTED)
         }
         try{
          excutor(resolve,reject)
         }catch(error){
             reject(error)
         }
       
      }
     #change(data,status){
    if(this.#state!== MyPromise.PENDING) return
    this.#result = data
    this.#state = status
    }
    
    #runMicroTask(func) {//放在微任务当中运行
    if (typeof process === 'object' && typeof process.nextTick === 'function') {
        process.nextTick(func)
      } else if (typeof MutationObserver === 'function') {
    const ob = new MutationObserver(func)
    const textNode = document.createTextNode('1')
    ob.observe(textNode, {
    characterData: true
    })
    textNode.data = '2'
    }else{//脱离环境,就没法模拟微任务。事件循环是环境能力而不是语言能力。
    setTimeout(func,0)
    }
  }
    #isPromiseLike(value){
       if (value !== null && (typeof value === 'object' || typeof value ==='function')) {
            return typeof value.then === 'function'
        } else {
        return false
        }
    }
    #runOne(callBack, resolve, reject){
    this. #runMicroTask(()=>{
         if (typeof callBack !== 'function') {
            const settled = this.#state === MyPromise.FULFILLED ? resolve : reject
            settled(this.#result)
           } 
           else {
             try {
                const data = callBack(this.#result)
                if (this.#isPromiseLike(data)) {
                data.then(resolve, reject)
                } else {
                    resolve(data)
                  }
            } catch (error) {
                reject(error)
               }
            }
    })
    }
    #run(){
        if(this.#state === MyPromise.PENDING) return 
        while (this.#handler.length > 0) {
            const { onFULfilled, onRejected, resolve, reject } = this.#handler.shift()
            if (this.#state === MyPromise.FULFILLED) {
                this.#runOne(onFULfilled, resolve, reject)
            } else {
                this.#runOne(onRejected, resolve, reject)
               }
            }
    }
    then(onFULfilled,onRejected){
        return new MyPromise((resolve,reject)=>{
      //将要执行的函数收集起来,当状态发生变更的时候调用。
    this.#handler.push({ onFULfilled, onRejected, resolve, reject});
    this.#run()
    })
   }

runMicroTaskfu辅助方法判断是在node环境还是在浏览器环境进行模拟微任务。其他情况无法模拟微任务。因为事件循环是环境给予的能力,脱离环境就办不到了。

我们来验证一下 截屏2024-12-22 23.37.15.png

到这为止我们的promise大体也写完了。代码格式可能不太优雅,见谅。因为我复制过来没有格式。自己手动打的空格。也可能上述代码哪里出了纰漏因为上述代码没有测试。可以看下面这个完整吧,复制到编译器看。

class MyPromise {

static PENDING = 'Pending'

static FULFILLED = 'Fulfilled'

static REJRCTED = 'Rejected'

  


#result = undefined

#state = MyPromise.PENDING

#handler = []

constructor(excutor) {

const resolve = (result) => {

this.#change(result, MyPromise.FULFILLED)

}

const reject = (reason) => {

this.#change(reason, MyPromise.REJRCTED)

}

excutor(resolve, reject)

}

  


#isPrimiseLike(value) {//判断是不是一个promise,只需要符合A+规范就可以。

if (value !== null && (typeof value === 'object' || typeof value === 'function')) {

return typeof value.then === 'function'

} else {

return false

}

}

  


#runMicroTask(func) {//放在微任务当中运行

if (typeof process === 'object' && typeof process.nextTick === 'function') {

process.nextTick(func)

} else if (typeof MutationObserver === 'function') {

const ob = new MutationObserver(func)

const textNode = document.createTextNode('1')

ob.observe(textNode, {

characterData: true

})

textNode.data = '2'

}else{//脱离环境,就没法模拟微任务。事件循环是环境能力而不是语言能力。

setTimeout(func,0)

}

}

#change(data, status) {

if (this.#state !== MyPromise.PENDING) return

this.#result = data

this.#state = status

this.#run()

}

#runOne(callBack, resolve, reject) {

this.#runMicroTask(() => {

if (typeof callBack !== 'function') {

const settled = this.#state === MyPromise.FULFILLED ? resolve : reject

settled(this.#result)

} else {

try {

const data = callBack(this.#result)

if (this.#isPrimiseLike(data)) {

data.then(resolve, reject)

} else {

resolve(data)

}

} catch (error) {

reject(error)

}

}

})

}

#run() {

if (this.#state === MyPromise.PENDING) return

while (this.#handler.length > 0) {

const { onFULfilled, onRejected, resolve, reject } = this.#handler.shift()

if (this.#state === MyPromise.FULFILLED) {

this.#runOne(onFULfilled, resolve, reject)

} else {

this.#runOne(onRejected, resolve, reject)

}

}

  


}

then(onFULfilled, onRejected) {

return new MyPromise((resolve, reject) => {

//将要执行的函数收集起来,当状态发生变更的时候调用。

this.#handler.push({ onFULfilled, onRejected, resolve, reject });

this.#run()

})

}

}