Promise的完全实现

551
6月30日 · 2021年

开发中Promise是及其常用的语法,基本上对于异步的处理大都是通过Promise来进行完成。Promise规范有很多,ES6最终采用的是Promise/A+ 规范,所以以下代码也基本是基于这个规范来进行编写的。

首先我们先列举Promise的所有实例方法跟静态方法

实例方法

  • then: new Promise((resolve, reject) => {...}).then(() => {console.log('rsolve成功回调')}, () => {console.log('reject失败回调')})
  • catch: new Promise((resolve, reject) => {...}).catch(() => {console.log('reject失败方法')})
  • finally: new Promise((resolve, reject) => {...}).finally(() => {console.log('成功失败都进入')})
  • 以上方法调用都将返回新的Promise

静态方法

  • resolve: Promise.resolve(value)返回Promise实例
  • reject: Promise.reject(value)返回Promise实例
  • all: Promise.all(promises): 传入数组格式的Promise并返回新的Promise实例,成功便按照顺序把值返回出来,其中一个失败则直接变成失败
  • race: Promise.race(promises): 传入数组格式的Promise并返回新的Promise实例,成功与失败取决第一个的完成方式

Promise状态一旦确定变不可再发生变化,有以下三个状态:pendingfulfilledrejected
Promise在浏览器中的实现是放于微任务队列中的,需要做微任务的处理(JavaScript中的Event Loop(事件循环)机制

1.声明Promise的实例方法

class Promise {
  _value
  _state = 'pending'
  _queue = []
  constructor(fn) {
    if (typeof fn !== 'function') {
      throw 'Promise resolver undefined is not a function'
    }
    /* 
      new Promise((resolve, reject) => {
        resolve: 成功
        reject: 失败
      })
    */
    fn(this._resolve.bind(this), this._reject.bind(this))
  }

  // 接收1-2参数,第一个为成功的回调,第二个为失败的回调
  then(onFulfilled, onRejected) {
    // 有可能已经resolve了,因为Promise可以提前resolve,然后then方法后面注册
    if (this._state === 'fulfilled') {
      onFulfilled?.(this._value)
      return
    }
    // reject同理
    if (this._state === 'rejected') {
      onRejected?.(this._value)
      return
    }
    // Promise还没有完成,push到一个队列,到时候完成的时候,执行这个队列里面对应的函数
    this._queue.push({
      onFulfilled,
      onRejected,
    })
  }

  // 接收失败的回调
  catch(onRejected) {
    // 相当于直接调用then传入失败的回调
    this.then(null, onRejected)
  }

  // 成功与失败都执行的回调
  finally(onDone) {
    const fn = () => onDone()
    this.then(fn, fn)
  }
  // 成功resolve
  _resolve(value) {
    // 状态确定了,就不再发生变化了
    if (this._state !== 'pending') return
    this._state = 'fulfilled'

    // 把值存起来,当再次调用的时候直接取这个值就行,因为Promise一旦确定就不会发生改变了
    this._value = value

    // 执行前面.then方法里面push函数形式的参数,这样就执行对应的方法了。
    this._queue.forEach((callback) => {
      callback.onFulfilled?.(this._value)
    })
  }

  // 失败reject
  _reject(error) {
    // 状态确定了,就不再发生变化了
    if (this._state !== 'pending') return
    this._state = 'rejected'
    this._value = error
    this._queue.forEach((callback) => {
      callback.onRejected?.(this._value)
    })
  }
}javascript 复制代码

调用逻辑:

  1. 通过then方法传入函数形式的参数,也就是onFulfilled => then((onFulfilled, onRejected) => {...})

  2. then方法中把onFulfilled函数放入_queue这个集合中。 => this._queue.push({ onFulfilled, onRejected })

  3. 等异步回调完成,执行resolve函数,这个时候就调用_queue收集好的通过then方法注册的函数。统一执行这些函数,这样就达到异步回调完成,执行对应的then方法里面的函数

// 结果打印
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
})
p.then((res) => {
  console.log(res) // => success
})

// reject
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('fail')
  }, 1000)
})
p1.catch((res) => {
  console.log(res) // => fail
})

// finally
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  }, 1000)
})
p2.finally(() => {
  console.log('done') // => done
})javascript 复制代码

在线代码演示

2. 微任务处理以及返回Promise

a. 进行微任务处理

在浏览器中 Promise 完成之后会被推入微任务,所以我们也需要进行这块的处理。浏览器中使用MutationObserver,node可以使用process.nextTick

class Promise {
  ...
  // 推入微任务
  _nextTick(fn) {
    if (typeof MutationObserver !== 'undefined') { // 浏览器通过MutationObserver实现微任务的效果
      // 这块可以单独拿出来共用,避免不必要的开销,不然每次都需要生成节点。
      const observer = new MutationObserver(fn)
      let count = 1
      const textNode = document.createTextNode(String(count))
      observer.observe(textNode, {
        characterData: true
      })
      textNode.data = String(++count)
    } else if (typeof process.nextTick !== 'undefined') { // node端通过process.nextTick来实现
      process.nextTick(fn)
    } else {
      setTimeout(fn, 0)
    }
  }
  // 成功resolve
  _resolve(value) {
    // 状态确定了,就不再发生变化了
    if (this._state !== 'pending') return
    // 推入微任务
    this._nextTick(() => {
      this._state = 'fulfilled'
      this._value = value
      this._queue.forEach((callback) => {
        callback.onFulfilled?.(this._value)
      })
    })
  }

  // 失败reject
  _reject(error) {
    // 状态确定了,就不再发生变化了
    if (this._state !== 'pending') return
    // 推入微任务
    this._nextTick(() => {
      this._state = 'rejected'
      this._value = error
      this._queue.forEach((callback) => {
        callback.onRejected?.(this._value)
      })
    })
  }
  ...
}javascript 复制代码

效果演示

b. 返回Promise进行链式调用

通常Promise会处理多个异步请求,有时候请求之间是有相互依赖关系的。

例如:

const getUser = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        userId: '123'
      })
    }, 500)
  })
}

const getDataByUser = (userId) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // ....
      resolve({a: 1})
    }, 500)
  })
}

// 使用
getUser().then((user) => {
  return getDataByUser(user.userId)
}).then((res) => {
  console.log(res)// {a: 1}
})javascript 复制代码

getDataByUser依赖getUser请求回来的用户信息,这里就需要用到Promise链式的调用,下面我们来改动我们的代码

class Promise {
  constructor(fn) {
    fn(this._resolve.bind(this), this._reject.bind(this))
  }
  ...
  // 1. 这时候then方法需要返回新的Promise了,因为需要进行链式调用,并且下一个then方法接受上一个then方法的值
  // 2. 返回的Promise肯定是一个新的Promise,不然就会共用状态跟返回结果了。
  // 3. 把上一个then方法中的返回值当做下一个Promise resolve的值
  then(onFulfilled, onRejected) {
    // 返回新的Promise
    return new Promise((resolve, reject) => {
      // 有可能已经resolve了,因为Promise可以提前resolve,然后then方法后面注册,这个时候可以直接把值返给函数就好了
      if (this._state === 'fulfilled' && onFulfilled) {
        this._nextTick(onFulfilled.bind(this, this._value))
        return
      }
      if (this._state === 'rejected' && onRejected) {
        this._nextTick(onRejected.bind(this, this._value))
        return
      }
      /* 
        把当前Promise的then方法的参数跟新的Promise的resolve, reject存到一起,以此来做关联。
        这样就能把上一个Promise中的onFulfilled与新的Promise中的resolve两个关联到一块,然后便可以做赋值之类的操作了。reject同理
      */
      this._queue.push({
        onFulfilled,
        onRejected,
        resolve,
        reject
      })
    })
  }
  // reject同理
  _resolve(value) {
    // 状态确定了,就不再发生变化了
    if (this._state !== 'pending') return

    // 上面示例里面其实返回的是一个Promise,而不是直接返回的值,所以,这里我们需要做一个特殊处理。
    // 就是resolve()的值如果是Promise的对象,我们需要解析Promise的结果,然后在把值传给resolve
    if (typeof value === 'object' && typeof value.then === 'function') {
      // 我们可以把当前_resolve方法传递下去,因为then方法中的参数,一经下个Promise resolve,便会执行then方法对应的参数,然后把对应的值传入。
      // 这样就能取到Promise中的值
      // this._resove => obj.onFulfilled?.(this._value)
      // this._reject => obj.onRejected?.(this._value)
      value.then(this._resolve.bind(this), this._reject.bind(this))
      return
    }

    // 推入微任务
    this._nextTick(() => {
      this._state = 'fulfilled'
      this._value = value
      this._queue.forEach((obj) => {
        // 接受onFulfilled返回值
        const val = obj.onFulfilled?.(this._value)
        // reoslve这个值,此时 onFulfilled 是当前Promise then方法中的第一个参数: Promise.then((res) => {consolle.log(res)})
        // obj.resolve是新的Promise的resolve函数,这样就把then方法中的返回值传给下一个Promise
        obj.resolve(val)
      })
    })
  }
  ...
}
javascript 复制代码

效果演示

调用逻辑:

  1. 微任务采用MutationObserverprocess.nextTick来进行实现

  2. Promise链式调用,这里通过把then方法中的(onFulfilled, onRejected)参数与新返回的Promise中的(resolve, reject)关联到一起。

  3. 一旦上一个Promise成功,调用onFulfilled函数,就可以把onFulfilled中返回的值,放到新的Promise的resolve中。

  4. 如果遇到resolve的值是Promise对象,递归进行解析,然后再把值返回出去

完整代码

class Promise {
  _value
  _state = 'pending'
  _queue = []
  constructor(fn) {
    if (typeof fn !== 'function') {
      throw new Error('Promise resolver undefined is not a function')
    }
    /* 
      new Promise((resolve, reject) => {
        resolve: 成功
        reject: 失败
      })
    */
    fn(this._resolve.bind(this), this._reject.bind(this))
  }

  // 接收1-2参数,第一个为成功的回调,第二个为失败的回调
  then(onFulfilled, onRejected) {
    // 返回新的Promise
    return new Promise((resolve, reject) => {
      // 有可能已经resolve了,因为Promise可以提前resolve,然后then方法后面注册,这个时候可以直接把值返给函数就好了
      if (this._state === 'fulfilled' && onFulfilled) {
        this._nextTick(onFulfilled.bind(this, this._value))
        return
      }
      if (this._state === 'rejected' && onRejected) {
        this._nextTick(onRejected.bind(this, this._value))
        return
      }
      // 把当前Promise的then方法的参数跟新的Promise的resolve, reject存到一起,以此来做关联
      this._queue.push({
        onFulfilled,
        onRejected,
        resolve,
        reject
      })
    })
  }

  // 接收失败的回调
  catch(onRejected) {
    return this.then(null, onRejected)
  }

  // 成功与失败都执行的回调
  finally(onDone) {
    return this.then((value) => {
      onDone()
      return value
    }, (value) => {
      // console.log(value)
      onDone()
      throw value
    })
  }

  // 推入微任务
  _nextTick(fn) {
    if (typeof MutationObserver !== 'undefined') { // 浏览器
      // 这块可以单独拿出来共用,避免不必要的开销,不然每次都需要生成节点。
      const observer = new MutationObserver(fn)
      let count = 1
      const textNode = document.createTextNode(String(count))
      observer.observe(textNode, {
        characterData: true
      })
      textNode.data = String(++count)
    } else if (typeof process.nextTick !== 'undefined') { // node
      process.nextTick(fn)
    } else {
      setTimeout(fn, 0)
    }
  }
  // 成功resolve
  _resolve(value) {
    // 状态确定了,就不再发生变化了
    if (this._state !== 'pending') return

    // 上面示例里面其实返回的是一个Promise,而不是直接返回的值,所以,这里我们需要做一个特殊处理。
    // 就是如果resolve()的如果是Promise的对象,我们需要解析Promise的结果,然后在把值传给resolve
    if (typeof value === 'object' && typeof value.then === 'function') {
      // 我们可以把当前_resolve方法传递下去,因为then方法中的参数,一经下个Promise resolve,便会执行then方法对应的参数,然后把对应的值传入。
      // 这样就能取到Promise中的值
      // this._resove => obj.onFulfilled?.(this._value)
      // this._reject => obj.onRejected?.(this._value)
      value.then(this._resolve.bind(this), this._reject.bind(this))
      return
    }

    // 推入微任务
    this._nextTick(() => {
      this._state = 'fulfilled'
      this._value = value
      this._queue.forEach((obj) => {
        // 使用try catch 来捕获onFulfilled存在函数内部错误的情况
        try {
          // 接受onFulfilled返回值,如果不存在,把this._value往下传递
          const val = obj.onFulfilled ? obj.onFulfilled(this._value) : this._value
          // reoslve这个值,此时 onFulfilled 是当前Promise then方法中的第一个参数: Promise.then((res) => {consolle.log(res)})
          // obj.resolve是新的Promise的resolve函数,这样就把then方法中的返回值传给下一个Promise
          obj.resolve(val)
        } catch (e) {
          obj.reject(e)
        }
      })
    })
  }

  // 失败reject
  _reject(error) {
    if (this._state !== 'pending') return
    this._nextTick(() => {
      this._state = 'rejected'
      this._value = error
      this._queue.forEach((obj) => {
        try {
          const val = obj.onRejected ? obj.onRejected(this._value) : this._value
          // 当前 reject执行完毕之后,会返回新的Promise,应该是能正常resolve的,所以这里要用 resolve, 不应该继续使用reject来让下个Promise执行失败流程
          obj.resolve(val)
        } catch (e) {
          obj.reject(e)
        }
      })
    })
  }
}javascript 复制代码

声明Promise的静态方法

总共有4个静态方法: Promise.resolvePromise.rejectPromise.allPromise.race

class Promise {
  ...
  /**
   * 直接resolve
   */
  static resolve(value) {
    // 是Promise直接返回
    if (value instanceof Promise) {
      return value
    } else if (typeof value === 'object' && typeof value.then === 'function') {
      // 传入的对象含有then方法
      const then = value.then
      return new Promise((resolve) => {
        then.call(value, resolve)
      })
    } else {
      // 正常返回值,直接返回新的Promise在resolve这个值
      return new Promise((resolve) => resolve(value))
    }
  }

  /**
   * 直接reject, 测试下reject在Promise.reject中没做特殊处理,直接返回即可
   */
  static reject(value) {
    return new Promise((resolve, reject) => reject(value))
  }

  /**
   * 传入数组格式的`Promise`并返回新的`Promise`实例,成功便按照顺序把值返回出来,其中一个失败则直接变成失败
   */
  static all(promises) {
    return new Promise((resolve, reject) => {
      let count = 0
      let arr = []
      // 按照对应的下标push到数组里面
      promises.forEach((promise, index) => {
        // 转换成Promise对象
        Promise.resolve(promise).then((res) => {
          count++
          arr[index] = res
          if (count === promises.length) {
            resolve(arr)
          }
        }, err => reject(err))
      })
    })
  }
  
  /**
   * 传入数组格式的`Promise`并返回新的`Promise`实例,成功与失败取决第一个的完成方式
   */
  static race(promises) {
    return new Promise((resolve, reject) => {
      promises.forEach((promise, index) => {
        // 转换成Promise对象
        Promise.resolve(promise).then((res) => {
          // 谁先执行直接resolve, 或reject
          resolve(res)
        }, err => reject(err))
      })
    })
  }
  ...
}javascript 复制代码

Promise实现完整代码

class Promise {
  _value
  _state = 'pending'
  _queue = []
  constructor(fn) {
    if (typeof fn !== 'function') {
      throw new Error('Promise resolver undefined is not a function')
    }
    /* 
      new Promise((resolve, reject) => {
        resolve: 成功
        reject: 失败
      })
    */
    fn(this._resolve.bind(this), this._reject.bind(this))
  }

  /**
   * 接收1-2参数,第一个为成功的回调,第二个为失败的回调
   *
   * @param {*} onFulfilled
   * @param {*} onRejected
   * @return {*} 
   * @memberof Promise
   */
  then(onFulfilled, onRejected) {
    // 返回新的Promise
    return new Promise((resolve, reject) => {
      // 有可能已经resolve了,因为Promise可以提前resolve,然后then方法后面注册,这个时候可以直接把值返给函数就好了
      if (this._state === 'fulfilled' && onFulfilled) {
        this._nextTick(onFulfilled.bind(this, this._value))
        return
      }
      if (this._state === 'rejected' && onRejected) {
        this._nextTick(onRejected.bind(this, this._value))
        return
      }
      // 把当前Promise的then方法的参数跟新的Promise的resolve, reject存到一起,以此来做关联
      this._queue.push({
        onFulfilled,
        onRejected,
        resolve,
        reject
      })
    })
  }

  /**
   * 接收失败的回调
   *
   * @param {*} onRejected
   * @return {*} 
   * @memberof Promise
   */
  catch(onRejected) {
    return this.then(null, onRejected)
  }

  /**
   * 成功与失败都执行的回调
   *
   * @param {*} onDone
   * @return {*} 
   * @memberof Promise
   */
  finally(onDone) {
    return this.then((value) => {
      onDone()
      return value
    }, (value) => {
      onDone()
      // 直接报错,可以在try catch中捕获错误
      throw value
    })
  }

  /**
   * 直接resolve
   *
   * @static
   * @param {*} value
   * @return {*} 
   * @memberof Promise
   */
  static resolve(value) {
    if (value instanceof Promise) {
      return value
    } else if (typeof value === 'object' && typeof value.then === 'function') {
      // 传入的对象含有then方法
      const then = value.then
      return new Promise((resolve) => {
        then.call(value, resolve)
      })
    } else {
      return new Promise((resolve) => resolve(value))
    }
  }

  /**
   * 直接reject, 测试下reject在Promise.reject中没做特殊处理
   *
   * @static
   * @param {*} value
   * @return {*} 
   * @memberof Promise
   */
  static reject(value) {
    return new Promise((resolve, reject) => reject(value))
  }

  /**
   * 传入数组格式的`Promise`并返回新的`Promise`实例,成功便按照顺序把值返回出来,其中一个失败则直接变成失败
   *
   * @static
   * @param {*} promises
   * @memberof Promise
   */
  static all(promises) {
    return new Promise((resolve, reject) => {
      let count = 0
      let arr = []
      if (Array.isArray(promises)) {
        if (promises.length === 0) {
          return resolve(promises)
        }
        promises.forEach((promise, index) => {
          // 转换成Promise对象
          Promise.resolve(promise).then((res) => {
            count++
            arr[index] = res
            if (count === promises.length) {
              resolve(arr)
            }
          }, err => reject(err))
        })
        return
      } else {
        reject(`${promises} is not Array`)
      }
    })
  }
  
  /**
   * 传入数组格式的`Promise`并返回新的`Promise`实例,成功与失败取决第一个的完成方式
   *
   * @static
   * @param {*} promises
   * @return {*} 
   * @memberof Promise
   */
  static race(promises) {
    return new Promise((resolve, reject) => {
      if (Array.isArray(promises)) {
        promises.forEach((promise, index) => {
          // 转换成Promise对象
          Promise.resolve(promise).then((res) => {
            resolve(res)
          }, err => reject(err))
        })
      } else {
        reject(`${promises} is not Array`)
      }
    })
  }

  // 推入微任务
  _nextTick(fn) {
    if (typeof MutationObserver !== 'undefined') { // 浏览器
      // 这块可以单独拿出来共用,避免不必要的开销,不然每次都需要生成节点。
      const observer = new MutationObserver(fn)
      let count = 1
      const textNode = document.createTextNode(String(count))
      observer.observe(textNode, {
        characterData: true
      })
      textNode.data = String(++count)
    } else if (typeof process.nextTick !== 'undefined') { // node
      process.nextTick(fn)
    } else {
      setTimeout(fn, 0)
    }
  }
  // 成功resolve
  _resolve(value) {
    // 状态确定了,就不再发生变化了
    if (this._state !== 'pending') return

    // 上面示例里面其实返回的是一个Promise,而不是直接返回的值,所以,这里我们需要做一个特殊处理。
    // 就是如果resolve()的如果是Promise的对象,我们需要解析Promise的结果,然后在把值传给resolve
    if (typeof value === 'object' && typeof value.then === 'function') {
      // 我们可以把当前_resolve方法传递下去,因为then方法中的参数,一经下个Promise resolve,便会执行then方法对应的参数,然后把对应的值传入。
      // 这样就能取到Promise中的值
      // this._resove => obj.onFulfilled?.(this._value)
      // this._reject => obj.onRejected?.(this._value)
      value.then(this._resolve.bind(this), this._reject.bind(this))
      return
    }

    // 通过打印测试,如果直接在线程里进行resolve, 状态跟值好像是直接就改变了,并没有执行完主流程,在执行微任务的时候进行修改的。
    // 所以把状态改变和值的修改移出了微任务,只有在走回调的时候才通过微任务进行处理
    this._state = 'fulfilled'
    this._value = value

    // 推入微任务
    this._nextTick(() => {
      this._queue.forEach((obj) => {
        // 使用try catch 来捕获onFulfilled存在函数内部错误的情况
        try {
          // 接受onFulfilled返回值,如果不存在,把this._value往下传递
          const val = obj.onFulfilled ? obj.onFulfilled(this._value) : this._value
          // reoslve这个值,此时 onFulfilled 是当前Promise then方法中的第一个参数: Promise.then((res) => {consolle.log(res)})
          // obj.resolve是新的Promise的resolve函数,这样就把then方法中的返回值传给下一个Promise
          obj.resolve(val)
        } catch (e) {
          obj.reject(e)
        }
      })
    })
  }

  // 失败reject
  _reject(error) {
    if (this._state !== 'pending') return
    this._state = 'rejected'
    this._value = error

    this._nextTick(() => {
      this._queue.forEach((obj) => {
        try {
          // 用户传入的函数内部错误捕获
          if (obj.onRejected) {
            const val = obj.onRejected(this._value)
            // 当前 reject执行完毕之后,会返回新的Promise,应该是能正常resolve的,所以这里要用 resolve, 不应该继续使用reject来让下个Promise执行失败流程
            obj.resolve(val)
          } else {
            // 递归传递reject错误
            obj.reject(this._value)
          }
        } catch (e) {
          obj.reject(e)
        }
      })
    })
  }
}javascript 复制代码

完整演示效果

本项目完整代码:GitHub

以上就是Promise的实现方案,当然这个跟完整的Promises/A+规范是有区别的。这里只是用做于学习之用。

QQ群 公众号
前端打杂群
QQ群:810018802
冬瓜书屋
公众号:冬瓜书屋

我自己新创建了一个相互学习的群,无论你是准备入坑的小白,还是半路入行的同学,希望我们能一起分享与交流。
QQ群:810018802, 点击加入

  • 表情
  • 预览
可使用部分markdown语法

大神到访数:29097