2023年都过去一半了,你还不会手写Promise?

1,521 阅读12分钟

手写Promise不是为了应付面试,是为了锻炼自己的编程思维,理解Promise核心。在工作中能用好Promise。从而也是对自己职业生涯的一种交代。如果一味的觉得没什么意义,那这篇文章就不用往下看了!

首先写任何API或者框架不要追求一步到位,需要慢慢的分析,一步一个脚印的来写。最后完善。

看到这里废话不多说。直接开整!

    class myPromise{
         constructor(executor) {
             //这个玩意对应的就是外面传进来的函数,
             executor(this.resolve.bind(this),this.reject.bind(this)) 
         }
         
         resolve(data){ //成功的回调
            console.log(data) 
         }
         
         reject(res){ //失败的回调
          console.log(res) 
         }
    }
    
     new myPromise(function (resolve, reject) {
        resolve('成功!')
        //reject('失败!')
     })
    

写到这里咱就直接测试一下吧! 去浏览器打印,发现2个回调都能正常调用!非常棒

image.png

但是Promise的状态只能是一个要么成功,要么失败,要么等待,并且是不可逆的,一但状态变了,就已经固定了!所以得加一个状态管理。同时在成功与失败的回调里面加上判断。

    class myPromise{
         #state = 'pending' //初始化状态 pending
         constructor(executor) {
             //这个玩意对应的就是外面传进来的函数,
             executor(this.resolve.bind(this),this.reject.bind(this)) 
         }
         
         resolve(data){ //成功的回调
             if(this.#state !== 'pending') return
             this.#state = 'fulfilled' //改变状态fulfilled
             console.log(data) 
         }
         
         reject(res){ //失败的回调
             if(this.#state !== 'pending') return
             this.#state = 'rejected' //改变状态rejected
             console.log(res) 
         }
    }
    
     new myPromise(function (resolve, reject) {
        resolve('成功!')
        //reject('失败!')
     })
    

以上一个简单的Promise就写好了。 根据不同的状态来控制成功还是失败。

但是状态控制好了之后,我们还需要拿到结果呀。 用的Promise的小伙伴都知道,结果需要在then方法的回调函数里面拿。接下来我们就写 then方法吧

    class myPromise{
         #state = 'pending' //初始化状态 pending
         #result = undefined //存放成功或者失败的结果
         constructor(executor) {
             //这个玩意对应的就是外面传进来的函数,
             executor(this.resolve.bind(this),this.reject.bind(this)) 
         }
         
         resolve(data){ //成功的回调
             if(this.#state !== 'pending') return
             this.#state = 'fulfilled' //改变状态fulfilled
             this.#result = data //赋值成功的结果
         }
         
         reject(res){ //失败的回调
             if(this.#state !== 'pending') return
             this.#state = 'rejected' //改变状态rejected
             this.#result = res //赋值失败的结果
         }
         
         then(onFulfilled, onRejected){
             if(this.#state == 'fulfilled'){
                 onFulfilled(this.#result)  //如果状态是成功的,就把结果给then方法的第一个回调
             }else if(this.#state == 'rejected'){
                 onRejected(this.#result)   //如果状态是失败的,就把结果给then方法的第二个回调
             }
         }
    }
    
     new myPromise(function (resolve, reject) {
        resolve('成功!')
        //reject('失败!')
     }).then((success)=>{
         console.log(success)
     },(error)=>{
         console.log(error)
     })
    

以上Promise的then方法也写好了。 一个完整的Promise就实现了呀。 怎么样简单吗? 伙伴们!

但是在测试的时候我们会发现一个问题。我们都知道Promise的出现,是为了优化异步代码。 那如果我们用定时器模拟一个1秒才返回成功的异步请求呢? 此时还能打印出成功吗?

    new myPromise(function (resolve, reject) {
      setTimeout(() => {
        resolve('成功')
      }, 1000)
    }).then((success) => {
      console.log(success)
    }, (error) => {
      console.log(error)
    })

此刻异步就失效了,什么也没打印。那要怎么样才能解决这个问题呢。 很简单,我们只需要在resolve与reject这2个方法改变状态的时候调用一下onFulfilled或者onRejected

但是细心的小伙伴会想,onFulfilled与onRejectedthen方法的2个回调,只能在then这个方法里面调, resolve与reject这2个改变状态的方法,没办法直接调用呀。这个时候我们可以在then方法先收集onFulfilled与onRejected,然后保存到一个变量里面!

    class myPromise{
         #state = 'pending' //初始化状态 pending
         #result = undefined //存放成功或者失败的结果
         #stack = null //定义一个存放onFulfilled, onRejected的变量
         constructor(executor) {
             //这个玩意对应的就是外面传进来的函数,
             executor(this.resolve.bind(this),this.reject.bind(this)) 
         }
         
         resolve(data){ //成功的回调
             if(this.#state !== 'pending') return
             this.#state = 'fulfilled' //改变状态fulfilled
             this.#result = data //赋值成功的结果
             this.#stack.onFulfilled(this.#result) //状态改变的时候调用
         }
         
         reject(res){ //失败的回调
             if(this.#state !== 'pending') return
             this.#state = 'rejected' //改变状态rejected
             this.#result = res //赋值失败的结果
             this.#stack.onRejected(this.#result) //状态改变的时候调用
         }
         
         then(onFulfilled, onRejected){
            if (this.#state == 'fulfilled') { 
              onFulfilled(this.#result) //如果状态是成功的,就把结果给then方法的第一个回调
            } else if (this.#state == 'rejected') {
              onRejected(this.#result) //如果状态是失败的,就把结果给then方法的第二个回调
            } else {   
              this.#stack = { //如果状态是pending等待的,就把结果先收集好。等状态改变再去调用
                onFulfilled, onRejected
              }
            }
         }
    }
    
   
    new myPromise(function (resolve, reject) {
      setTimeout(() => {
        resolve('成功')
      }, 1000)
    }).then((success) => {
      console.log(success)
    }, (error) => {
      console.log(error)
    })

现在是不是异步的问题就解决啦。但是又引出一个问题。如果我多次调用then方法呢? 始终只会打印出一个"成功"

    let data = new myPromise(function (resolve, reject) {
        setTimeout(() => {
            resolve('成功')
        }, 1000)
    })

    data.then((success) => {
      console.log(success)
    }, (error) => {
      console.log(error)
    })

    data.then((success) => {
      console.log(success)
    }, (error) => {
      console.log(error)
    })

这个问题也很好解决,我们只需要把#stack这个变量变成一个数组用来存放就搞定了!

      class myPromise{
         #state = 'pending' //初始化状态 pending
         #result = undefined //存放成功或者失败的结果
         #stack = [] //定义一个存放onFulfilled, onRejected的变量
         constructor(executor) {
             //这个玩意对应的就是外面传进来的函数,
             executor(this.resolve.bind(this),this.reject.bind(this)) 
         }
         
         resolve(data){ //成功的回调
             if(this.#state !== 'pending') return
             this.#state = 'fulfilled' //改变状态fulfilled
             this.#result = data //赋值成功的结果
             this.#stack.forEach(item => { //循环数组
                  item.onFulfilled(this.#result)
             })
         }
         
         reject(res){ //失败的回调
             if(this.#state !== 'pending') return
             this.#state = 'rejected' //改变状态rejected
             this.#result = res //赋值失败的结果
             this.#stack.forEach(item => { //循环数组
                  item.onRejected(this.#result)
             })
         }
         
         then(onFulfilled, onRejected){
            if (this.#state == 'fulfilled') { 
              onFulfilled(this.#result) //如果状态是成功的,就把结果给then方法的第一个回调
            } else if (this.#state == 'rejected') {
              onRejected(this.#result) //如果状态是失败的,就把结果给then方法的第二个回调
            } else {   
              this.#stack.push({ //如果状态是pending等待的,就把结果先收集好。等状态改变再去调用
                onFulfilled, onRejected
              })
            }
         }
    }

讲到这里我们忽略了一个关键性的问题。then方法是一个可链式调用的。我们这种写法能链式调用吗?

    let data = new myPromise(function (resolve, reject) {
        setTimeout(() => {
            resolve('成功')
        }, 1000)
    })

    data.then((success) => {
      console.log(success)
    }).then((success) => {
      console.log(success)
    })

很显然,这样会报错! 所以我们要让then方法返回一个Promise。接下来再改造一下

      class myPromise{
         #state = 'pending' //初始化状态 pending
         #result = undefined //存放成功或者失败的结果
         #stack = [] //定义一个存放onFulfilled, onRejected的变量
         constructor(executor) {
             //这个玩意对应的就是外面传进来的函数,
             executor(this.resolve.bind(this),this.reject.bind(this)) 
         }
         
         resolve(data){ //成功的回调
             if(this.#state !== 'pending') return
             this.#state = 'fulfilled' //改变状态fulfilled
             this.#result = data //赋值成功的结果
             this.#stack.forEach(item => { //状态改变的时候调用
                  item.onFulfilled(this.#result)
             })
         }
         
         reject(res){ //失败的回调
             if(this.#state !== 'pending') return
             this.#state = 'rejected' //改变状态rejected
             this.#result = res //赋值失败的结果
             this.#stack.forEach(item => { //状态改变的时候调用
                  item.onRejected(this.#result)
             })
            
         }
         
         then(onFulfilled, onRejected){
           return  new myPromise((resolve, reject)=>{
                if (this.#state == 'fulfilled') { 
                  onFulfilled(this.#result) //如果状态是成功的,就把结果给then方法的第一个回调
                } else if (this.#state == 'rejected') {
                  onRejected(this.#result) //如果状态是失败的,就把结果给then方法的第二个回调
                } else {   
                  this.#stack.push({ //如果状态是pending等待的,就把结果先收集好。等状态改变再去调用
                    onFulfilled, onRejected
                  })
                }
             })
           
         }
    }

此时就不报错了,也返回了一个Promise。 但是Promise还有一个特点,就是当第一个then不是一个函数的时候,返回值会穿透给下一个then。 这句话很绕口,也不好理解。 直接看代码吧

    let data = new myPromise(function (resolve, reject) {
        setTimeout(() => { resolve('成功') }, 1000)
    })

    data.then(null).then((success) => {  //第一个then的回调是一个null
      console.log(success)
    })

那么怎么解决这个问题呢? 我们只需要判断一下then的回调方法是不是一个函数,如果不是函数就把它的参数传入Promise的resolve与reject做处理

      class myPromise {
      #state = 'pending' //初始化状态 pending
      #result = undefined //存放成功或者失败的结果
      #stack = [] //定义一个存放onFulfilled, onRejected的变量
      constructor(executor) {
        //这个玩意对应的就是外面传进来的函数,
        executor(this.resolve.bind(this), this.reject.bind(this))
      }

      resolve(data) { //成功的回调
        if (this.#state !== 'pending') return
        this.#state = 'fulfilled' //改变状态fulfilled
        this.#result = data //赋值成功的结果
        this.#stack.forEach(item => { //状态改变的时候调用
          if (typeof item.onFulfilled == 'function') { //判断是否是一个函数
            item.onFulfilled(this.#result)
          } else {
            item.resolve(this.#result)
          }
        })
      }

      reject(res) { //失败的回调
        if (this.#state !== 'pending') return
        this.#state = 'rejected' //改变状态rejected
        this.#result = res //赋值失败的结果
        this.#stack.forEach(item => { //状态改变的时候调用
          if (typeof item.onRejected == 'function') { //判断是否是一个函数
            item.onRejected(this.#result)
          } else {
            item.reject(this.#result)
          }
        })

      }

      then(onFulfilled, onRejected) {
        return new myPromise((resolve, reject) => {
          if (this.#state == 'fulfilled') {
            if (typeof onFulfilled == 'function') { //判断是否是一个函数
              onFulfilled(this.#result) //如果状态是成功的,就把结果给then方法的第一个回调
            } else {
              resolve(this.#result)
            }
          } else if (this.#state == 'rejected') {

            if (typeof onRejected == 'function') { //判断是否是一个函数
              onRejected(this.#result) //如果状态是成功的,就把结果给then方法的第一个回调
            } else {
              reject(this.#result)
            }

          } else {
            this.#stack.push({ //如果状态是pending等待的,就把结果先收集好。等状态改变再去调用
              onFulfilled, onRejected, resolve, reject
            })
          }
        })
      }
    }

这个问题解决后,还引出一个相似的问题。就是then方法里面return的值,可以被下一个then方法的回调接收到

    let data = new myPromise(function (resolve, reject) {
        setTimeout(() => { resolve('成功') }, 1000)
    })

    data.then(()=>{
        return 111
    }).then((success) => {  
      console.log(success) //111
    })

针对这个问题,我们只需要把onFulfilled与onRejected执行后的返回值给resolve与reject就行了

     class myPromise {
      #state = 'pending' //初始化状态 pending
      #result = undefined //存放成功或者失败的结果

      #stack = [] //定义一个存放onFulfilled, onRejected的变量
      constructor(executor) {
        //这个玩意对应的就是外面传进来的函数,
        executor(this.resolve.bind(this), this.reject.bind(this))
      }

      resolve(data) { //成功的回调
        if (this.#state !== 'pending') return
        this.#state = 'fulfilled' //改变状态fulfilled
        this.#result = data //赋值成功的结果
        this.#stack.forEach(item => { //状态改变的时候调用

          if (typeof item.onFulfilled == 'function') {
            const data = item.onFulfilled(this.#result)
            try {
              item.resolve(data) //返回值作为参数 ,传给resolve
            } catch (err) {
              item.reject(err)
            }
          } else {
            item.resolve(this.#result)
          }
        })
      }

      reject(res) { //失败的回调
        if (this.#state !== 'pending') return
        this.#state = 'rejected' //改变状态rejected
        this.#result = res //赋值失败的结果
        this.#stack.forEach(item => { //状态改变的时候调用
          if (typeof item.onRejected == 'function') {
            const data = item.onRejected(this.#result)
            try {
              item.resolve(data) //返回值作为参数 ,传给resolve
            } catch (err) {
              item.reject(err)
            }
          } else {
            item.reject(this.#result)
          }
        })

      }

      then(onFulfilled, onRejected) {
        return new myPromise((resolve, reject) => {
          if (this.#state == 'fulfilled') {
            if (typeof onFulfilled == 'function') {
              const data = onFulfilled(this.#result) //如果状态是成功的,就把结果给then方法的第一个回调
              try {
                resolve(data)  //返回值作为参数 ,传给resolve
              } catch (err) {
                reject(err)
              }
            } else {
              resolve(this.#result)
            }
          } else if (this.#state == 'rejected') {

            if (typeof onRejected == 'function') {
              const data = onRejected(this.#result) //如果状态是成功的,就把结果给then方法的第一个回调
              try {
                resolve(data)  //返回值作为参数 ,传给resolve
              } catch (err) {
                reject(err)  
              }
            } else {
              reject(data)
            }

          } else {
            this.#stack.push({ //如果状态是pending等待的,就把结果先收集好。等状态改变再去调用
              onFulfilled, onRejected, resolve, reject
            })

          }
        })
      }
    }

那如果就是then方法里面return的值又是一个promise呢?可以被下一个then接收吗?

    let data = new myPromise(function (resolve, reject) {
        setTimeout(() => { resolve('成功') }, 1000)
    })

    data.then(()=>{
        return new myPromise(function (resolve, reject) {
            setTimeout(() => { resolve('成功') }, 1000)
        })
    }).then((success) => {  
      console.log(success) //成功
    })

这里还需要对细节做一下改造。

    function isPromise(val) { //判断是否是一个promise
        if (val !== null && (typeof val == 'object' || typeof val == 'function')) {
            return typeof val.then === 'function'
        }
    }

    class myPromise {
      #state = 'pending' //初始化状态 pending
      #result = undefined //存放成功或者失败的结果

      #stack = [] //定义一个存放onFulfilled, onRejected的变量
      constructor(executor) {
        //这个玩意对应的就是外面传进来的函数,
        executor(this.resolve.bind(this), this.reject.bind(this))
      }

      resolve(data) { //成功的回调
        if (this.#state !== 'pending') return
        this.#state = 'fulfilled' //改变状态fulfilled
        this.#result = data //赋值成功的结果
        this.#stack.forEach(item => { //状态改变的时候调用

          if (typeof item.onFulfilled == 'function') {
            const data = item.onFulfilled(this.#result)
            try {
              if (isPromise(data)) { //如果是promise 就调用 then 
                data.then(item.resolve, item.reject)
              } else {
                item.resolve(data)
              }
            } catch (err) {
              item.reject(err)
            }
          } else {
            item.resolve(this.#result)
          }
        })
      }

      reject(res) { //失败的回调
        if (this.#state !== 'pending') return
        this.#state = 'rejected' //改变状态rejected
        this.#result = res //赋值失败的结果
        this.#stack.forEach(item => { //状态改变的时候调用
          if (typeof item.onRejected == 'function') {
            const data = item.onRejected(this.#result)
            try {
              if (isPromise(data)) {
                data.then(item.resolve, item.reject) //如果是promise 就调用 then 
              } else {
                item.resolve(data)
              }
            } catch (err) {
              item.reject(err)
            }
          } else {
            item.reject(this.#result)
          }
        })

      }

      then(onFulfilled, onRejected) {
        return new myPromise((resolve, reject) => {
          if (this.#state == 'fulfilled') {
            if (typeof onFulfilled == 'function') {
              const data = onFulfilled(this.#result) //如果状态是成功的,就把结果给then方法的第一个回调
              try {
                if (isPromise(data)) {
                  data.then(resolve, reject) //如果是promise 就调用 then 
                } else {
                  resolve(data)
                }
              } catch (err) {
                reject(err)
              }
            } else {
              resolve(this.#result)
            }
          } else if (this.#state == 'rejected') {

            if (typeof onRejected == 'function') {
              const data = onRejected(this.#result) //如果状态是成功的,就把结果给then方法的第一个回调
              try {
                if (isPromise(data)) {
                  data.then(resolve, reject)//如果是promise 就调用 then 
                } else {
                  resolve(data)
                }

              } catch (err) {
                reject(err)
              }
            } else {
              reject(data)
            }

          } else {
            this.#stack.push({ //如果状态是pending等待的,就把结果先收集好。等状态改变再去调用
              onFulfilled, onRejected, resolve, reject
            })

          }
        })
      }
    }

这样我们手写的promise 就全部写完了! 我们只需要要简单的测试一下,结合官方的promise 组合我们自己写的都能成功返回。

    let data = new myPromise((res, rej) => {
      setTimeout(() => { res('成功') }, 1000)

    }).then(res => {
      return new Promise((res, rej) => {
        setTimeout(() => { res('成功') }, 1000)
      })
    })

    async function getData() {
      let res = await data
      console.log(res) //成功
    }
    getData()

由于上面重复代码太多,咱们再优化一波


    function isPromise(val) {
        if (val !== null && (typeof val == 'object' || typeof val == 'function')) {
            return typeof val.then === 'function'
        }
    }

    const PENDING = 'pending'
    const FULFILLED = 'fulfilled'
    const REJECTED = 'rejected'

    class myPromise {
      #state = 'pending' //初始化状态 pending
      #result = undefined //存放成功或者失败的结果
      #stack = [] //定义一个存放onFulfilled, onRejected的变量
      constructor(executor) {
        executor(this.resolve.bind(this), this.reject.bind(this))
      }

      setState(state, result) {
        this.#state = state //改变状态fulfilled
        this.#result = result //赋值成功的结果
        this.walk()
      }

      setData(fn, resolve, reject) {
        if (typeof fn == 'function') {
          try {
            const data = fn(this.#result)
            if (isPromise(data)) {
              data.then(resolve, reject)
            } else {
              resolve(data)
            }
          } catch (err) {
            reject(err)
          }
        } else {
          resolve(this.#result)
        }
      }


      walk() {
        if (this.#state == 'pending') return
        while (this.#stack.length) {
          const { onFulfilled, onRejected, resolve, reject } = this.#stack.shift()
          if (this.#state == FULFILLED) {
            this.setData(onFulfilled, resolve, reject)
          } else if (this.#state == REJECTED) {
            this.setData(onRejected, resolve, reject)
          }
        }
      }

      resolve(data) { //成功的回调
        if (this.#state !== 'pending') return
        this.setState('fulfilled', data)
      }

      reject(res) { //失败的回调
        if (this.#state !== 'pending') return
        this.setState('rejected', res)
      }

      then(onFulfilled, onRejected) {
        return new myPromise((resolve, reject) => {
          this.#stack.push({ //收集
            onFulfilled, onRejected, resolve, reject
          })
          this.walk()
        })
      }
    }

现在手写Promise就全部完工了。。小伙伴们学废了吗?