加载中...

手写一个符合Promise A+规范的Promise实现

吴佳
2020-05-29 14:33:42
分类:JavaScript
4498
2
0

前言

记得之前发过一篇关于Promise文章的讲解,不过都不是很深入,只是对使用上的理解,所以这次我将会带着各位通过JavaScript来实现一个Promise,并且是符合规范的,最后可以通过promises-aplus-tests来进行跑测。
整个实现主要通过Promise A+规范来做的,可以参考以下地址:
https://promisesaplus.com/

正文

接下来的内容我将直接贴出源码,因为我在写的时候都以逐行加了注释来说明代码理解,所以就不会再来逐行解读了,如各位从其中发现任何问题欢迎留言指正

utils.js 文件


const {
  PENDING,
  FULFILLED,
  REJECTED,
  getType,
  isArray,
  isObject,
  isFunction
} = {
  PENDING: 'pending',
  FULFILLED: 'fulfilled',
  REJECTED: 'rejected',
  getType: (t) => (v) => Object.prototype.toString.call(v) === `[object ${t}]`,
  isArray: (v) => getType('Array')(v),
  isObject: (v) => getType('Object')(v),
  isFunction: (v) => getType('Function')(v),
}

// 解析promise,这里将会处理返回的promise或者其它情况下promise的状态让其直接变为完成状态并将参数值传入到下一个then
const resolvePromise = (promise2, x, resolve, reject) => {
  let caller = false // 定义一个开关,为了让promise的状态一旦确定则不能再做修改

  // 如果promise是它自己,避免自己等待自己,直接抛错
  if (promise2 === x) {
    return reject(
      new TypeError('Chaining cycle detected for promise #<Promise>')
    )
  }

  // 如果x是对象或者是一个函数的时候 那么它可能是一个promise,接下来将进一步解析。 这里是为了兼容第三方promise库,例如:q es6-promise
  if ((x && isObject(x)) || isFunction(x)) {
    try {
      const then = x.then
      // 确定then是一个函数的时候,那么肯定是一个promise
      if (isFunction(then)) {
        // 执行then函数
        then.call(
          x,
          y => {
            if (caller) return null
            caller = true
            // 递归解析,直到不是一个promise
            resolvePromise(promise2, y, resolve, reject)
          },
          e => {
            if (caller) return null
            caller = true
            // 如果发生错误,将直接变为拒绝状态并返回错误信息
            reject(e)
          }
        )
      } else {
        // 如果不是一个promise,则直接将其状态变为完成并返回其值
        resolve(x)
      }
    } catch (err) {
      if (caller) return null
      caller = true
      // 发生错误这里直接将状态变为拒绝并返回错误信息
      reject(err)
    }
  } else {
    // 当x是一个普通值,那么将直接变为完成状态,并返回其值
    resolve(x)
  }
}

// 专门用来处理then的onFulfilled Or onRejected 回调
const onFulfilledOrOnRejectedHandler = (
  promise2,
  onFulfilledOrOnRejectedCallBack,
  resolve,
  reject,
  value
) => {
  // 此处的定时器为了等待Promise的实例完成
  setTimeout(() => {
    try {
      // 执行then的resolve or reject函数并传入其值,通过一个变量x去拿到当前resolve执行后的返回值
      const x = onFulfilledOrOnRejectedCallBack(value)
      // 解析then的resolve or reject执行,如果返回一个promise或者其它值情况的处理
      resolvePromise(promise2, x, resolve, reject)
    } catch (err) {
      // 如果返回发生错误,则直接reject
      reject(err)
    }
  }, 0)
}

module.exports = {
  PENDING,
  FULFILLED,
  REJECTED,
  isArray,
  isObject,
  isFunction,
  onFulfilledOrOnRejectedHandler
}

主文件 promise.js

const {
  PENDING,
  FULFILLED,
  REJECTED,
  isArray,
  isObject,
  isFunction,
  onFulfilledOrOnRejectedHandler
} = require('./utils')

class Promise {
  constructor(executor) {
    this.status = PENDING
    this.doneValue = undefined // 同步executor执行后,保存resolve函数参数值
    this.resason = undefined // 同步executor执行后,保存reject函数的参数值

    // 发布订阅模式。then方法执行时如发现状态未变,则订阅then方法执行的 完成 Or 拒绝 回调
    this.doneCallbacks = []
    this.failCallbacks = []

    const resolve = (doneValue) => {
      // 如果值是一个promise
      if (doneValue instanceof Promise) {
        // 将递归解析resolve中的参数直到不是一个promise对象
        return doneValue.then(resolve, reject)
      }
      // 判断只有是等待状态的时候才进行成功处理,为了一旦状态发生改变将不会再改变状态
      if (this.status === PENDING) {
        this.status = FULFILLED
        this.doneValue = doneValue

        // 执行then方法的resolve订阅回调
        this.doneCallbacks.forEach((fn) => fn())
      }
    }

    const reject = (resason) => {
      // 判断只有是等待状态的时候才进行拒绝处理,为了一旦状态发生改变将不会再改变状态
      if (this.status === PENDING) {
        this.status = REJECTED
        this.resason = resason

        // 执行then方法的reject订阅回调
        this.failCallbacks.forEach((fn) => fn())
      }
    }

    // 异常处理,一旦发生错误直接将状态变为拒绝并返回错误信息
    try {
      // 同步执行 executor promise的回调
      executor(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }

  // 内部定时器的作用是为了等待Promise的实例完成再执行
  then(onFulfilled, onRejected) {
    // 如果 onFulfilled Or onRejected 不是函数,则将其忽略,默认赋值一个函数返回其值,为了让值往下穿透
    onFulfilled = isFunction(onFulfilled) ? onFulfilled : v => v
    onRejected = isFunction(onRejected) ? onRejected : (err) => { throw err }

    // then的执行必须返回一个新的promise,形成无限链式调用(也就是形成递归)
    const promise2 = new Promise((resolve, reject) => {
      let value = ''
      let onFulfilledOrOnRejectedCallBack = ''
      // 如果状态已变成完成状态 则保存onFulfilled回调 并保存完成的donevalue
      if (this.status === FULFILLED) {
        onFulfilledOrOnRejectedCallBack = onFulfilled
        value = this.doneValue
      }

      // 如果状态已变成完成状态 则保存onRejected回调 并保存拒绝的resason
      if (this.status === REJECTED) {
        onFulfilledOrOnRejectedCallBack = onRejected
        value = this.resason
      }

      // 执行对应状态的 onFulfilled Or onRejected 并传入对应的value
      if (isFunction(onFulfilledOrOnRejectedCallBack)) {
        setTimeout(() => {
          onFulfilledOrOnRejectedHandler(
            promise2,
            onFulfilledOrOnRejectedCallBack,
            resolve,
            reject,
            value
          )
        }, 0)
      }

      // 如果状态不变
      if (this.status === PENDING) {
        // 订阅then的完成回调
        this.doneCallbacks.push(() => {
          setTimeout(() => {
            onFulfilledOrOnRejectedHandler(
              promise2,
              onFulfilled,
              resolve,
              reject,
              this.doneValue
            )
          }, 0)
        })

        // 订阅then的拒绝回调
        this.failCallbacks.push(() => {
          setTimeout(() => {
            onFulfilledOrOnRejectedHandler(
              promise2,
              onRejected,
              resolve,
              reject,
              this.resason
            )
          }, 0)
        })
      }
    })

    return promise2
  }

  // catch的回调利用then方法的实现
  catch(failCallback) {
    return this.then(null, failCallback)
  }

  // finally 是无论如何都会执行的
  // 如果返回一个promise,那么将会等待这个promise执行完毕
  finally(callback) {
    return this.then(
      x => Promise.resolve(callback()).then(() => x),
      e =>
        Promise.reject(callback()).then(() => {
          throw e
        })
    )
  }

  // resolve 的静态方法
  static resolve(v) {
    return new Promise((resolve) => {
      resolve(v)
    })
  }

  // reject 的静态方法
  static reject(err) {
    return new Promise((resolve, reject) => {
      reject(err)
    })
  }

  static all(promises) {
    // 看一下进来的参数是不是一个数组
    promises = isArray(promises) ? promises : []

    let fulfilledCount = 0 // 状态变完成的个数
    let promisesLength = promises.length // 需要完成的个数
    let results = new Array(promisesLength) // 设置结果数组长度

    return new Promise((resolve, reject) => {
      // 如果是个空数组,那么将直接变为完成状态,并传入空数组参数值
      if (promisesLength === 0) return resolve([])
      // 遍历数组中的promise
      promises.forEach((promise, index) => {
        // 判断是不是一个promise
        if (isObject(promise) && isFunction(promise.then)) {
          promise.then(
            (value) => {
              // 向结果数组中存入 对应返回数据值
              results[index] = value
              // 当等于了需完成的个数,说明已全部都处理完了,那么就直接将状态变为完成,返回最终数据
              if (++fulfilledCount === promisesLength) resolve(results)
            },
            (err) => reject(err) // 只要一个发生错误,那么直接变为失败,并返回失败原因
          )
        } else {
          // 如果不是一个promise,将直接存值
          results[index] = promise
          if (++fulfilledCount === promisesLength) resolve(results)
        }
      })
    })
  }

  // 看谁快
  static race(promises) {
    promises = isArray(promises) ? promises.filter(item => isObject(item) && isFunction(item.then)) : []

    return new Promise((resolve, reject) => {
      promises.forEach((promise) => {
        promise.then(
          (value) => resolve(value),
          (err) => reject(err)
        )
      })
    })
  }
}

// 延迟执行,这个主要用于promise A+规范跑测使用
Promise.defer = Promise.deferred = () => {
  let dfd = {}
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

module.exports = Promise

结语

以上就是全部的代码了,代码不是很多,Promise A+规范主要在于then方法,其它辅助方法都比较容易实现。

下面是这个源码的仓库地址,如果想直接测试一下可以拉下来跑一跑

https://github.com/wujiabk/promise

扫码关注后,回复“资源”免费领取全套视频教程

前端技术专栏

2

发表评论(共0条评论)

请输入评论内容
啊哦,暂无评论数据~