手写完整Promise A+ 【附源码】

73 阅读5分钟

写在前面的话

看了市面上的一些手写Promise,基本上都不怎么规范,要么无法处理异步,要么没有trycatch,要么没有发布订阅的功能,要么没有实现链式调用等等。那么今天就来实现一个符合Promise A+规范的代码。既然来看手写,那么肯定对Promise都有一定的了解,接下来我们直接上手。

Promise A+规范

Promise/A+规范原文: promisesaplus.com/

Promise/A+规范译文: www.ituring.com.cn/article/665…

以下为测试成功截图

image-20220723204501253

Promise基础版

const SUCCESS = 'fulfilled'
const FAIL = 'rejected'
const PENDING = 'pending'
class Promise {
  constructor(executor) {
    // 默认是等待态
    this.status = PENDING 
    this.value = undefined
    this.reason = undefined
    const resolve = value => {
      // 只有状态为 PENDING 时才允许修改状态,因为promise状态不可逆
      if (this.status === PENDING) {
        this.value = value
        this.status = SUCCESS
      }
    }
    const reject = reason => {
      if (this.status === PENDING) {
        this.reason = reason
        this.status = FAIL
      }
    }
    // new Promise() 报错的时候也会执行 reject()
    try {
      // 立即执行
      executor(resolve, reject) 
    } catch (e) {
      reject(e)
    }
  }
  then(onFulfilled, onRejected) {
    if (this.status === SUCCESS) {
      onFulfilled(this.value)
    }
    if (this.status === FAIL) {
      onRejected(this.reason)
    }
  }
}

module.exports = Promise

Promise处理异步

const SUCCESS = 'fulfilled'
const FAIL = 'rejected'
const PENDING = 'pending'
class Promise {
  constructor(executor) {
    // 默认是等待态
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
    // 存储成功的所有的回调 只有pending的时候才存储
    this.onResolvedCallbacks = []
    // 存储所有失败的
    this.onRejectedCallbacks = []
    const resolve = value => {
      // 只有状态为 PENDING 时才允许修改状态,因为promise状态不可逆
      if (this.status === PENDING) {
        this.value = value
        this.status = SUCCESS
        this.onResolvedCallbacks.forEach(fn => fn())
      }
    }
    const reject = reason => {
      if (this.status === PENDING) {
        this.reason = reason
        this.status = FAIL
        this.onRejectedCallbacks.forEach(fn => fn())
      }
    }
    // executor 中抛出错误时也会执行 reject()
    try {
      // 立即执行
      executor(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }
  /**
   * @description:
   * then方法会用到一个发布订阅模式,处理 executor 中的异步代码.
   * 如果resolve()的是一个Promise,会自动将这个promise执行,并且采用他的状态,如果成功会将成功的结果向下一层传递,
   * 如果then方法中的成功或者失败 执行的时候发生错误 会走下一个then的失败的回调
   * 如果then方法返回了一个失败的promise他会走外层then的失败的回调
   *  1、(then中传递的函数)判断成功/失败函数的返回结果
   *  2、 如果是 promise 则,采用它的结果
   *  3、 如果不是promise 则,继续将结果传递下去
   * @param {*} onFulfilled
   * @param {*} onRejected
   */
  then(onFulfilled, onRejected) {
    //  同步处理
    if (this.status === SUCCESS) {
      onFulfilled(this.value)
    }
    if (this.status === FAIL) {
      onRejected(this.reason)
    }
    // 如果是PENDING状态,那么应该将函数存起来 分组存放(异步处理)
    if (this.status === PENDING) {
      // 如果是异步,先订阅好
      // push() 参数为什么是函数?
      // this.onResolvedCallbacks.push(onFulfilled(this.value));
      // 这样做也可以,但是无法添加一些逻辑,比如在添加函数前做一些逻辑,所以这里采用函数的方式
      this.onResolvedCallbacks.push(() => {
        // 这里可以添加额外的逻辑
        onFulfilled(this.value)
      })
      this.onRejectedCallbacks.push(() => {
        onRejected(this.reason)
      })
    }
  }
}

module.exports = Promise

Promise链式调用(核心)

const PENDING = 'PENDING'
const SUCCESS = 'FULFILLED'
const FAIL = 'REJECTED'

function resolvePromise(promise2, x, resolve, reject) {
  // 如果 promise2 和 x 相同,抛出错误
  if (promise2 === x) {
    return reject(
      new TypeError('TypeError: Chaining cycle detected for promise #<Promise>')
    )
  }
  // 判断x的类型
  // promise 有n种实现 都符合了这个规范 兼容别人的promise
  // 严谨 🇬应该判断 别人的promise 如果失败了就不能在调用成功 如果成功了不能在调用失败
  let called

  // 怎么判断 x是不是一个promise 看他有没有then方法
  if (typeof x === 'function' || (typeof x === 'object' && x != null)) {
    try {
      // 取then方法可能会出错,所以需要使用 trycatch
      let then = x.then
      if (typeof then === 'function') {
        // 如果 then 为 一个函数,我就认为他是一个promise
        // 直接使用取好的then,而不是使用x.then,否则会在次取值,有可能第一次取值没有报错,第二次取值就报错了
        then.call(
          x,
          y => {
            // 如果promise是成功的就把结果向下传,如果失败的就让下一个人也失败
            if (called) return
            called = true // 防止多次调用成功和失败
            // resolve(y)
            // 为什么要使用 递归? 查看1.test.js 的情况, resolve()返回 promise
            resolvePromise(promise2, y, resolve, reject) // 递归
          },
          r => {
            if (called) return
            called = true
            // 报错的时候就直接往下走,不用再担心 是不是 peomise 了
            reject(r)
          }
        )
      } else {
        // {then:()=>{}}
        // 说明 x 是一个普通对象,直接成功即可
        resolve(x)
      }
    } catch (e) {
      // 为了辨别这个promise 防止调用多次
      if (called) return
      called = true
      reject(e)
    }
  } else {
    // x是个? 常量
    resolve(x)
  }
}
class Promise {
  constructor(executor) {
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
    this.onResolvedCallbacks = []
    this.onRejectedCallbacks = []
    const resolve = value => {
      if (this.status === PENDING) {
        this.value = value
        this.status = SUCCESS
        this.onResolvedCallbacks.forEach(fn => fn())
      }
    }
    const reject = reason => {
      if (this.status === PENDING) {
        this.reason = reason
        this.status = FAIL
        this.onRejectedCallbacks.forEach(fn => fn())
      }
    }
    try {
      executor(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }
  // 同一个promise then 多次
  then(onFulfilled, onRejected) {
    // 处理 2.test.js 中的情况
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : err => {
            // 将失败向下传递
            throw err
          }
    let promise2
    // 可以不停的调用then方法,返还了一个新的promise
    // 异步的特点 等待当前主栈代码都执行后才执行
    promise2 = new Promise((resolve, reject) => {
      if (this.status === SUCCESS) {
        // 为什么要使用 setTimeout ? 如果不使用 setTimeout ,promise2 则会报错,涉及到代码的执行顺序问题,需要先 new完后再将结果赋值给 promise2 可以去掉  setTimeout 打印一下 promise2 看看
        // setTimeout作用: 为了保证 promise2 已经 new 完了
        setTimeout(() => {
          // try catch 用于 捕获 onFulfilled 函数的异常,比如 在执行 onFulfilled 函数的时候抛错, 或者 onFulfilled 函数中 手动抛出错误
          // constructor 中的 try catch 无法捕获这里异步代码的异常
          try {
            // 调用当前then方法的结果,来判断当前这个promise2 是成功还是失败
            let x = onFulfilled(this.value)
            // 这里的x是普通值还是promise  如果是一个promise呢?
            // console.log('then-SUCCESS', promise2);
            // 判断 x 和 promise2 和 promise 的关系
            resolvePromise(promise2, x, resolve, reject)
          } catch (err) {
            console.log('promise2--catch ', err)
            reject(err)
          }
        })
      }
      if (this.status === FAIL) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (err) {
            reject(err)
          }
        })
      }
      if (this.status === PENDING) {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (err) {
              reject(err)
            }
          })
        })
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (err) {
              reject(err)
            }
          })
        })
      }
    })
    return promise2
  }
}

module.exports = Promise

1.test.js

const Promise = require('./promise')

const p = new Promise((resolve, reject) => {
  resolve('ok')
})

const p2 = p.then(res => {
  return new Promise((resolve,reject) => {
    setTimeout(() => {
      resolve(new Promise((resolve,reject) => {
        setTimeout(() => {
          resolve('1000')
        },1000);
      }))
    },1000)
  })
})

p2.then(
  data => {
    console.log('data', data)
  },
  err => {
    console.log('err', err)
  }
)

2.test.js

const Promise = require('./promise')

const p = new Promise((resolve, reject) => {
  resolve('ok')
})

p.then().then().then(data => {
  console.log('data', data);
})

完整Promise代码

github.com/SuYxh/Write…

测试

使用promises-aplus-tests这个库是否符合 promise A+规范

npm i promises-aplus-tests -g

然后在promise中添加:

Promise.defer = Promise.deferred = function(){
  let dfd = {};
  dfd.promise = new Promise((resolve,reject)=>{
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
}

然后执行:

promises-aplus-tests promise.js

image-20220723204501253