实现一个Promise

135 阅读6分钟

实现一个Promise

本篇文章将介绍并实现Promise的主要API,我不会详细介绍Promise的使用,适合对Promise有一定理解的同学阅读。

为了易于理解,本篇文章并没有考虑到部分比较特殊的情况。但是主要的实现会说清楚

1 起步

我们约定我们的类名为PromiseX

  • 首先,我们知道Promise有三种状态,默认的状态是pending,意味等待中

    const PENDING = 'pending'
    const FULFILLED = 'fulfilled'
    const REJECT = 'reject'
    class PromiseX {
        status = PENDING
    }
    
  • 其次,他接收一个立即执行函数,用于改变内部的状态,这个函数接收的参数分别为resolve和reject,来自于类的内部的方法

    class PromiseX {
      constructor(executor) {
        executor(this.resolve,this.reject)
      }
      status = PENDING
     // ------- 一下是两个改变状态的内部方法
      resolve() {
        // 由于Promise的状态只能更改一次,所以这里加了一个判断,如果状态改变过无法再次改变
        if (this.status !== PENDING) return
        this.status = FULFILLED
      }
      reject() {
        if (this.status !== PENDING) return
        this.status = REJECT
      }
    }
    
  • 然后,Promise最关键的是他能够使用then实现链式调用,这个then也是其内部的方法,他接收两个函数,分别是成功和失败的回调。不同的是这两个函数我们需要手动实现,而上面的resolve是Promise已经帮我们定义好了

    class PromiseX {
      //....省略上面的代码
      then(success,fail) {
        if(this.status ===FULFILLED) {
            success()
        } else if(this.status ===REJECT) {
            fail()
        }
      }
    }
    
  • 以上就实现了一个简单的Promise框架了,不过他还有很多缺点,接下来我们一一完善

2 锦上添花

1 加入异步逻辑

  • 我们上面的情况调用then 的时候只是解决了同步运行的情况下,假如说我在使用Promise 的时候使用了一个定时器,三秒后再调用resolve改变Promise的状态,但是你此时又调用了then方法,而我们此时的Promise的状态还是PENDING,所以要在then中加入一个pending的判断语句

  • 实现的逻辑是如果当前的状态还是等待中,我们在内部就使用一个数组存储一个回调函数,等待resolve执行后去调用这个回调函数的数组

    successFn = []
    failedFn = []
        // 加入异步逻辑,比如说你在一个定时器之中不能立马执行,需要把调用的成功函数的回调保存起来在修改状态的时候调用
        else {
          this.successFn.push(success)
          this.failedFn.push(success)
        }
    
  • 我们还需要在类内部顶一个value的值,用于存储使用resolve时传入的值

      value = undefined
      reason = undefined
      resolve = value => {
        if (this.status !== PENDING) return
        this.status = FULFILLED
        this.value = value
        // 这里再次调用的时候传入当前的value的值
        this.successFn.forEach(fn => fn(this.value))
      }
    
  • 此时的完整的代码

    const PENDING = 'pending'
    const FULFILLED = 'fulfilled'
    const REJECT = 'reject'
    class Promisex {
      constructor(executor) {
        executor(this.resolve, this.reject)
      }
      successFn = []
      failedFn = []
      status = PENDING
      value = undefined
      reason = undefined
      resolve = value => {
        if (this.status !== PENDING) return
        this.status = FULFILLED
        this.value = value
        // 这里再次调用的时候传入当前的value的值
        this.successFn.forEach(fn => fn(this.value))
      }
      reject = reason => {
        if (this.status !== PENDING) return
        this.status = REJECT
        this.reason = reason
        this.failedFn.forEach(fn => fn(this.reason))
      }
    ​
      then(success, fail) {
        if ((this.status === FULFILLED)) success(this.value)
        else if ((this.status === REJECT)) fail(this.reason)
        // 加入异步逻辑,比如说你在一个定时器之中不能立马执行,需要把调用的成功函数的回调保存起来在修改状态的时候调用
        else {
          this.successFn.push(success)
          this.failedFn.push(success)
        }
      }
    }
    
  • 来个例子测试一下吧

    下面这个例子,Promise内部的状态会在1秒之后才改变,then里面的成功回调也会在1s后执行

    const p = new Promisex((resolve, reject) => {
      setTimeout(() => {
        resolve()
        console.log('1')
      }, 1000)
    }).then(() => console.log('2'))
    

2 实现链式调用

  • 到现在,我们的then只能够链式调用一次,如果想要实现Promise真正的链式调用,我们可以让then返回的也是一个新的Promise,这样每个then返回的新对象都能调用里面的then方法了

      then(success, fail) {
        return new Promisex((resolve, reject) => {
          if (this.status === FULFILLED) {
            // 如果传入的回调函数success有返回值,可以通过resolve将它它传递给当前的promise
            const x = success(this.value)
            resolve(x)
          } else if (this.status === REJECT) fail(this.reason)
          else {
            this.successFn.push(success)
            this.failedFn.push(success)
          }
        })
      }
    

3 实现Promise.all

  • Promise.all方法使用介绍

    1. 他是一个静态的方法,意味着不需要实例就可以调用。所以使用static关键字声明

    2. 返回的是一个新的Promise,使用then方法可以得到按固定顺序结果得到的数组

    3. 直接看个例子吧,比较形象

      // 定义两个测试函数
      const fn1 = () => new Promise((resolve,reject) => {
          setTimeout(()=>{
              resolve('11111')
          },2000)
      })
      const fn2 = () => new Promise(resolve=>{
          resolve('2222')
      })
      Promise.all(['x','y',fn1(),fn2()]).then(result => console.log(result))
      
    4. 上面的结果是['x','y','1111','2222']也就说就Promise.all方法里面不管你是不是异步方案,得到的结果都是相同的顺序

  • 实现

    1. 首先我们考虑数组都是普通数据,而不是异步函数,这样我们很显然可以直接循环一遍数组,然后将结果存放到一个结果数组里面,当循环到最后一项的时候使用resolve保存这个Promise里面的数据即可

        static all(array) {
          return new Promisex ((resolve,reject)=>{
              const result = []
              array.forEach((item,index) => {
                  result.push(item)
                  if(index===array.length) resolve(result)
              })
          })
        }
      
    2. 但是,如果我们数组中如果存放的是异步函数,我们循环一遍数组就结束了,但是可能还有异步函数没执行完,所以我们记录当前这个异步函数的索引,当异步函数执行的时候推入相应索引的结果数组中,当所有索引都结束时,就可以resolve这个Promise

       // 这是一个静态方法,使用static定义
        static all(array) {
          return new Promisex((resolve, reject) => {
            // 结果数组
            const result = []
            // 完成了几项
            let finish = 0
            array.forEach((item, index) => {
              // 如果是一个promise的实例就使用then方法在他状态改变之后进行和赋值操作
              if (item instanceof Promisex) {
                item.then(
                  value => {
                    result[index] = value
                    finish++
                    if (finish === array.length) resolve(result)
                  },
                  reason => reject(reason)
                )
                // 如果只是一正常的值就直接放进结果数组了
              } else {
                result[index] = item
                finish++
                if (finish === array.length) resolve(result)
              }
            })
          })
        }
      

3 结语

本文章的Promise并不完整,并没有考虑到如调用then后返回的是又是Promise的情况,但是主要API的实现还是比较显而易见的,最后献上完整的代码

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECT = 'reject'
class Promisex {
  constructor(executor) {
    executor(this.resolve, this.reject)
  }
  // 这是一个静态方法,使用static定义
  static all(array) {
    return new Promisex((resolve, reject) => {
      // 结果数组
      const result = []
      // 完成了几项
      let finish = 0
      array.forEach((item, index) => {
        // 如果是一个promise的实例就使用then方法在他状态改变之后进行和赋值操作
        if (item instanceof Promisex) {
          item.then(
            value => {
              result[index] = value
              finish++
              if (finish === array.length) resolve(result)
            },
            reason => reject(reason)
          )
          // 如果只是一正常的值就直接放进结果数组了
        } else {
          result[index] = item
          finish++
          if (finish === array.length) resolve(result)
        }
      })
    })
  }
  successFn = []
  failedFn = []
  status = PENDING
  value = undefined
  reason = undefined
  resolve = value => {
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    // 这里再次调用的时候传入当前的value的值
    this.successFn.forEach(fn => fn(this.value))
  }
  reject = reason => {
    if (this.status !== PENDING) return
    this.status = REJECT
    this.reason = reason
    this.failedFn.forEach(fn => fn(this.reason))
  }
​
  then(success, fail) {
    if (this.status === FULFILLED) success(this.value)
    else if (this.status === REJECT) fail(this.reason)
    // 加入异步逻辑,比如说你在一个定时器之中不能立马执行,需要把调用的成功函数的回调保存起来在修改状态的时候调用
    else {
      this.successFn.push(success)
      this.failedFn.push(success)
    }
  }
}
​

\