JS进阶7 -- 手写Promise及其API

JS进阶7 -- 手写Promise及其API

  • JS进阶7 -- 手写Promise及其API
    • 1. 手写Promise核心功能
      • 1.1 构造函数
        • 需求
        • 核心步骤
        • 总结:
      • 1.2 状态及原因
        • 需求
        • 核心步骤
        • 总结
      • 1.3 then方法
        • 1.3.1 成功和失败回调
          • 需求
          • 核心步骤
          • 总结
        • 1.3.2 异步和多次调用
          • 需求
          • 核心步骤
          • 总结
      • 1.4 异步任务
        • 1.4.1 api补充
          • 需求
          • 异步api选取
          • 总结
        • 1.4.2 函数封装
          • 需求
          • 核心步骤
          • 总结
      • 1.5 链式编程
        • 1.5.1 fulfilled状态-处理异常和普通内容
          • 需求
          • 核心步骤
          • 总结
        • 1.5.2 fulfilled状态-处理返回Promise
          • 需求
          • 核心步骤
          • 总结
        • 1.5.3 fulfilled状态-处理重复引用
          • 需求
          • 核心步骤
          • 总结
        • 1.5.4 rejected状态
          • 需求
          • 核心步骤
          • 总结
        • 1.5.5 pending状态
          • 需求
          • 核心步骤
          • 总结
      • 小结
    • 2. 手写Promise实例及静态方法
      • 2.1 实例方法-catch
        • 需求
        • 核心步骤
        • 总结
      • 2.2 实例方法-finally
        • 需求
        • 核心步骤
        • 总结
      • 小结
      • 2.3 静态方法-resolve
        • 需求
        • 核心步骤
        • 总结
      • 2.4 静态方法-reject
        • 需求
        • 核心步骤
        • 总结
      • 2.5 静态方法-race
        • 需求
        • 核心步骤
        • 总结
      • 2.6 静态方法-all
        • 需求
        • 核心步骤
        • 总结
      • 2.7 静态方法-allSettled
        • 需求[-传送门](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled)
        • 核心步骤
        • 总结
      • 2.8 静态方法-any
        • 需求-[传送门](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/any)
        • 核心步骤
        • 总结
      • 小结
    • 3. 进行Promise/A+测试
      • 3.1 Promise\A+规范
      • 3.2 Promise\A+测试
        • 3.2.1 使用CommonJS暴露对象
        • 3.2.2 下包
        • 3.2.3 配置并执行命令
        • 3.2.4 执行测试
      • 总结
    • 参考资料

在 JS进阶6 – 类的创建和继承中我们学习了Javascript中类的创建和继承,本文将继续重点跟大家介绍JS中的第7个重点内容 – 手写Promise及其API。废话不多说,一起来看看吧!

JS进阶7 – 手写Promise及其API

这篇博客咱们会学习的有:

  1. 实现Promise的核心功能:
  2. Promise:
    1. 实例方法:catch,finally
    2. 静态方法:resolve,reject,race,all,allSettled,any
  3. Promise\A+标准,并跑通872个单元测试

首先明确Promise的核心用法

// 实例化 并管理异步任务
const p = new DIYPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
    // reject('error')
  }, 1000);
})

// then方法获取成功/失败结果
// 参数1:成功时执行的回调函数
// 参数2:失败时执行的回调函数
p.then(res => {
  console.log('res:', res)
  return 'success2'
}, err => {
  console.log('err:', err)
}).then(res2 => {
  console.log('res2:', res2)
})

1. 手写Promise核心功能

1.1 构造函数

需求
  1. 实现DIYPromise类,可以用如下的方式实例化
  2. 实例化时传入回调函数
    1. 回调函数立刻执行
    2. 回调函数接收函数resolvereject
const p = new DIYPromise((resolve, reject) => {
  resolve('success')
  // reject('error')
})
核心步骤
  1. 定义类DIYPromise
  2. 添加构造函数constructor
  3. 定义resolve/reject
  4. 执行回调函数
// 1. 定义类
class DIYPromise{
  // 2. 添加构造函数
  constructor(func) {
    // 3. 定义resolve/reject
    const resolve = (result) => {
      console.log('resolve-执行啦:', result)
    }
    const reject = (result) => {
      console.log('reject-执行啦:', result)
    }

    // 4. 执行回调函数
    func(resolve, reject)
  }
}
总结:

手写Promise-构造函数

  1. 定义类DIYPromise,内部添加构造函数constructor,构造函数需要接收回调函数func
  2. 在构造函数中定义resolvereject
  3. 构造函数内部调用func并将resolvereject传入:func(resolve,reject)

1.2 状态及原因

需求
  1. DIYPromise增加state属性,只能是如下3个值
    1. pending:待定,默认状态
    2. fulfilled:已兑现,操作成功
    3. rejected:已拒绝,操作失败
  2. DIYPromise增加result属性,记录成功/失败原因
  3. 调用resolvereject,修改状态,并记录成功/失败原因
const p = new DIYPromise((resolve, reject) => {
  resolve('success') // pending -> fulfilled
  // reject('error') // pending -> rejected
})
p.state // 状态
p.result // 原因
核心步骤
  1. 定义常量保存状态,避免硬编码
  2. DIYPromise中定义
    1. 属性:state保存状态,result成功/失败原因
    2. 修改state的私有方法,修改状态并记录result
    3. 注意:state只有在pending时,才可以修改,且不可逆
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class DIYPromise {
  // 1. 添加状态
  state = PENDING
  // 2. 添加原因
  result = undefined

  constructor(func) {
    // 3. 调整resolve/reject
    // 4. 状态不可逆
    // 改状态: pending->fulfilled
    // 记录原因
    const resolve = (result) => {
      if (this.state === PENDING) {
        this.state = FULFILLED
        this.result = result
      }
    }
    // 改状态: pending->rejected
    // 记录原因
    const reject = (result) => {
      if (this.state === PENDING) {
        this.state = REJECTED
        this.result = result
      }
    }

    func(resolve, reject)
  }
}
总结

手写Promise-状态、成功or失败原因

  1. 定义3个常量用来保存状态,pending,fulfilled,rejected
  2. DIYPromise内部定义属性stateresult分别用来保存状态和原因
  3. 调用resolve时传入具体原因,如果状态为pending则更改状态并记录兑现原因
  4. 调用reject时传入具体原因,如果状态为pending则更改状态并记录拒绝原因

1.3 then方法

1.3.1 成功和失败回调
需求
  1. then方法的回调函数1: 状态变为fulfilled时触发,并获取成功结果
  2. then方法的回调函数2: 状态变为rejected时触发,并获取失败原因
  3. then方法的回调函数1或2没有传递的特殊情况处理,参考:then方法的参数
const p = new DIYPromise((resolve, reject) => {
    resolve('success')
    // reject('error')
})
p.then(res => {
  console.log('成功回调:', res)
}, err => {
  console.log('失败回调:', err)
})
核心步骤
  1. 增加then方法,根据不同的状态执行对应的回调函数,并传入result
    1. 参数1:成功的回调函数
    2. 参数2:失败的回调函数
  2. 判断参数
    1. 没有传递onFulfilled,onRejected
    2. 设置默认值(参考文档)
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class DIYPromise {
  // 状态
  state = PENDING
  // 原因
  result = undefined

  // 构造函数
  constructor(func) {
    // pending->fulfilled
    const resolve = (result) => {
      if (this.state === PENDING) {
        this.state = FULFILLED
        this.result = result
      }
    }
    // pending->rejected
    const reject = (result) => {
      if (this.state === PENDING) {
        this.state = REJECTED
        this.result = result
      }
    }
    func(resolve, reject)
  }

  // 1. 添加实例方法
  then(onFulfilled, onRejected) {
    // 2. 参数判断(参考文档)
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
    onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }

    // 2.1 执行成功回调
    // 2.2 执行失败回调
    if (this.state === FULFILLED) {
      onFulfilled(this.result)
    } else if (this.state === REJECTED) {
      onRejected(this.result)
    }
  }
}
总结

手写Promise-then方法-成功和失败回调

  1. 添加then方法,接收2个回调函数:
    1. 成功回调onFulfilled
    2. 失败回调onRejected
  2. 判断传入的onFulfilledonRejected是否为函数,如果不是设置默认值
  3. 根据状态调用onFulfilledonRejected并传入兑现或拒绝原因
1.3.2 异步和多次调用
需求
  1. 实例化传入的回调函数,内部支持异步操作
  2. then方法支持多次调用(非链式编程)
const p = new DIYPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
    // reject('error')
  }, 2000);
})
p.then(res => {
  console.log('then1:', res)
}, err => {
  console.log('then1:', err)
})
p.then(res => {
  console.log('then2:', res)
}, err => {
  console.log('then2:', err)
})
核心步骤
  1. 定义属性:保存传入的回调函数:[]
  2. 保存回调函数:状态为pending
  3. 调用成功回调:resolve内部
  4. 调用失败回调:reject内部
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class DIYPromise {
  // 状态
  state = PENDING
  // 原因
  result = undefined
  // 1. 定义实例属性
  #handlers = [] // [{onFulfilled,onRejected}...]

  // 构造函数
  constructor(func) {
    // pending->fulfilled
    const resolve = (result) => {
      if (this.state === PENDING) {
        this.state = FULFILLED
        this.result = result
        // 3. 调用成功回调
        this.#handlers.forEach(({ onFulfilled }) => {
          onFulfilled(this.result)
        })
      }
    }
    // pending->rejected
    const reject = (result) => {
      if (this.state === PENDING) {
        this.state = REJECTED
        this.result = result
        // 4. 调用失败回调
        this.#handlers.forEach(({ onRejected }) => {
          onRejected(this.result)
        })
      }
    }
    func(resolve, reject)
  }

  // then方法
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
    onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }

    if (this.state === FULFILLED) {
      onFulfilled(this.result)
    } else if (this.state === REJECTED) {
      onRejected(this.result)
    } else if (this.state === PENDING) {
      // 2. 保存回调函数
      this.#handlers.push({
        onFulfilled, onRejected
      })
    }
  }
}
总结

手写Promise-then方法-异步和多次调用

  1. DIYPromise添加私有属性#handlers
    1. 保存then方法调用时状态为pending的回调函数
    2. 格式为对象数组:[{onFulfilled,onRejected}...]
  2. 构造函数内部调整resolvereject的逻辑:
    1. 调用resolve时取出数组#handlers中的所有onFulfilled回调函数进行调用
    2. 调用reject时取出数组#handlers中的所有onRejected回调函数进行调用

1.4 异步任务

1.4.1 api补充
  1. 传送门:MDN-queueMicrotask
  2. 传送门:MDN-queueMicrotask使用指南
  3. 传送门:MDN-setImmediate
  4. 传送门:MDN-MutationObserver
需求
  1. 如下代码打印结果为top success bottom
  2. 核心: 让then方法的回调函数以异步任务的方式执行
console.log('top')
const p = new DIYPromise((resolve, reject) => {
  resolve('success')
})
p.then(res => {
  console.log(res)
})
console.log('bottom')
异步api选取

这里我们参考vue2的做法:

  1. **vue2:**Promise.then、MutationObserver 、 setImmediate 、 setTimeout
  2. 我们选用:queueMicrotask 、MutationObserver 、 setTimeout
    选用原因:
  3. Promise.then: 我们是手写Promise,故不选用这个
  4. queueMicrotask :新式浏览器均支持,node11开始支持,ie不支持
  5. MutationObserver :新式浏览器均支持,ie11开始支持
  6. setImmediate: 新式浏览器只有edge支持,ie10开始支持
  7. setTimeout:浏览器支持,node支持
    测试代码:
// ------------- 异步任务1 queueMicrotask  -------------
// node v11 ie 不支持
// console.log('top')
queueMicrotask(() => {
  // ....
})
// console.log('bottom')

// ------------- 异步任务2 MutationObserver -------------
// node 不支持 ie11
console.log('top')
// 创建并返回一个新的观察器,它会在触发指定 DOM 事件时,调用指定的回调函数
const obs = new MutationObserver(() => {
  console.log('MutationObserver-run')
})
// 创建div
const divNode = document.createElement('div')
// 监听创建的div ,监听子节点改变
obs.observe(divNode, { childList: true })
// 修改内容触发回调函数
divNode.innerText = 'javascript 666'
console.log('bottom')

// ------------- 异步任务3 setTimeout -------------
// 这个都熟悉,就不测试了啦
总结

请问可以使用哪些方式开启异步任务:

  1. Promise.then: 我们是手写Promise,故不选用这个
  2. queueMicrotask :新式浏览器均支持,node11开始支持,ie不支持
  3. MutationObserver :新式浏览器均支持,ie11开始支持
  4. setImmediate: 新式浏览器只有edge支持,ie10开始支持
  5. setTimeout:浏览器支持,node支持
1.4.2 函数封装
需求
  1. 封装函数runMicrotask内部执行异步任务
  2. 使用runMicrotaskthen方法的回调函数为异步任务
核心步骤
  1. 封装函数runMicrotask并传入回调函数
  2. 内部依次判断:queueMicrotaskMutationObserversetTimeout并使用即可
  3. 使用runMicrotask开启执行异步任务即可
// 1. 定义函数
function runAsynctask(callback) {
  // 2. 调用核心api(queueMicrotask,MutationObserver,setTimeout)
  if (typeof queueMicrotask === 'function') {
    queueMicrotask(callback)
  } else if (typeof MutationObserver === 'function') {
    const obs = new MutationObserver(callback)
    const divNode = document.createElement('div')
    obs.observe(divNode, { childList: true })
    divNode.innerText = 'javascript 666'
  } else {
    setTimeout(callback, 0)
  }
}

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class DIYPromise {
  // 状态
  state = PENDING
  // 原因
  result = undefined
  // 回调函数数组
  #handlers = [] // [{onFulfilled,onRejected}...]

  // 构造函数
  constructor(func) {
    // pending->fulfilled
    const resolve = (result) => {
      if (this.state === PENDING) {
        this.state = FULFILLED
        this.result = result
        this.#handlers.forEach(({ onFulfilled }) => {
          onFulfilled(this.result)
        })
      }
    }
    // pending->rejected
    const reject = (result) => {
      if (this.state === PENDING) {
        this.state = REJECTED
        this.result = result
        this.#handlers.forEach(({ onRejected }) => {
          onRejected(this.result)
        })
      }
    }
    func(resolve, reject)
  }

  // then方法
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
    onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }

    // 3. 使用封装函数
    if (this.state === FULFILLED) {
      runAsynctask(() => {
        onFulfilled(this.result)
      })
    } else if (this.state === REJECTED) {
      runAsynctask(() => {
        onRejected(this.result)
      })
    } else if (this.state === PENDING) {
      this.#handlers.push({
        onFulfilled: () => {
          runAsynctask(() => {
            onFulfilled(this.result)
          })
        }, onRejected: () => {
          runAsynctask(() => {
            onRejected(this.result)
          })
        }
      })
    }
  }
}
总结

手写Promise-异步任务-函数封装

  1. 封装执行异步任务的函数
    1. 定义函数传入异步任务(回调函数)
    2. 内部根据实际情况判断并使用开启异步任务的api即可,比如queueMicrotask,MutationObserver
    3. 调用的api可以根据实际情况进行调整
    4. 如果都无法执行,使用setTimeout兜底
  2. 调整then中的逻辑,fulFilled,rejected,pending3种状态时的回调函数,使用封装的函数包装一次

1.5 链式编程

1.5.1 fulfilled状态-处理异常和普通内容
需求
  1. then的链式编程
  2. 目前只考虑then的第一个回调函数
    1. 返回普通值
    2. 内部出现异常
const p = new DIYPromise((resolve, reject) => {
  resolve(1)
})
p.then(res => {
  console.log(res)
  // throw 'throw-error'
  return 2
}).then(res => {
  console.log(res)
}, err => {
  console.log(err)
})
核心步骤
  1. 调整then方法,返回一个新的DIYPromise对象
  2. 使用try-catch捕获异常,并通过reject传递
  3. 内部获取onFulfilled的执行结果,并通过resolve传递
// 执行异步任务
function runAsynctask(callback) {
  if (typeof queueMicrotask === 'function') {
    queueMicrotask(callback)
  } else if (typeof MutationObserver === 'function') {
    const obs = new MutationObserver(callback)
    const divNode = document.createElement('div')
    obs.observe(divNode, { childList: true })
    divNode.innerText = 'itheima666'
  } else {
    setTimeout(callback, 0)
  }
}

/**
 * 链式编程-处理异常和普通内容(fulfilled状态)
 *  1. 返回新Promise实例
 *  2. 获取返回值
 *    2.1 处理返回值
 *    2.2 处理异常
*/
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class DIYPromise {
  // 状态
  state = PENDING
  // 原因
  result = undefined
  // 回调函数数组
  #handlers = [] // [{onFulfilled,onRejected}...]

  // 构造函数
  constructor(func) {
    // pending->fulfilled
    const resolve = (result) => {
      if (this.state === PENDING) {
        this.state = FULFILLED
        this.result = result
        this.#handlers.forEach(({ onFulfilled }) => {
          onFulfilled(this.result)
        })
      }
    }
    // pending->rejected
    const reject = (result) => {
      if (this.state === PENDING) {
        this.state = REJECTED
        this.result = result
        this.#handlers.forEach(({ onRejected }) => {
          onRejected(this.result)
        })
      }
    }
    func(resolve, reject)
  }

  // then方法
  // 1. 返回新Promise实例
  // 2. 获取返回值
  //    2.1 处理返回值
  //    2.2 处理异常
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
    onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }

    // 1. 返回新Promise实例
    const p2 = new HMPromise((resolve, reject) => {
      if (this.state === FULFILLED) {
        runAsynctask(() => {
          // 2. 获取返回值
          try {
            const x = onFulfilled(this.result)
            // console.log('x:', x)
            // 2.1 处理返回值
            resolve(x)
          } catch (error) {
            // 2.2 处理异常
            // console.log('捕获异常:', error)
            reject(error)
          }
        })
      }
      else if (this.state === REJECTED) {
        runAsynctask(() => {
          onRejected(this.result)
        })
      }
      else if (this.state === PENDING) {
        this.#handlers.push({
          onFulfilled: () => {
            runAsynctask(() => {
              onFulfilled(this.result)
            })
          }, onRejected: () => {
            runAsynctask(() => {
              onRejected(this.result)
            })
          }
        })
      }

    })

    return p2
  }
}
总结

手写Promie-链式编程-fulfilled状态-返回值+异常

  1. 链式编程的本质then方法会返回一个新的DIYPromise对象
  2. 将原本的代码迁移到返回的DIYPromise对象的回调函数中
  3. 内部通过try-catch捕获异常,出现异常通过reject传递异常
  4. 获取onFulfilled的执行结果,并通过resolve传递
1.5.2 fulfilled状态-处理返回Promise
需求
  1. then的链式编程
  2. 目前只考虑then的第一个回调函数
    1. 返回Promise
const p = new DIYPromise((resolve, reject) => {
  resolve(1)
})
p.then(res => {
  return new DIYPromise((resolve, reject) => {
    resolve(2)
    // reject('error')
  })
}).then(res => {
  console.log('p2:', res) // 2
}, err => {
  console.log('p2:', err) // error
})
核心步骤
  1. 判断是否为DIYPromise实例
  2. 调用then方法依次传入回调函数
// 执行异步任务
function runAsynctask(callback) {
  if (typeof queueMicrotask === 'function') {
    queueMicrotask(callback)
  } else if (typeof MutationObserver === 'function') {
    const obs = new MutationObserver(callback)
    const divNode = document.createElement('div')
    obs.observe(divNode, { childList: true })
    divNode.innerText = 'itheima666'
  } else {
    setTimeout(callback, 0)
  }
}

/**
 * 链式编程-处理Promise(fulfilled状态)
*/
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class DIYPromise {
  // 状态
  state = PENDING
  // 原因
  result = undefined
  // 回调函数数组
  #handlers = [] // [{onFulfilled,onRejected}...]

  // 构造函数
  constructor(func) {
    // pending->fulfilled
    const resolve = (result) => {
      if (this.state === PENDING) {
        this.state = FULFILLED
        this.result = result
        this.#handlers.forEach(({ onFulfilled }) => {
          onFulfilled(this.result)
        })
      }
    }
    // pending->rejected
    const reject = (result) => {
      if (this.state === PENDING) {
        this.state = REJECTED
        this.result = result
        this.#handlers.forEach(({ onRejected }) => {
          onRejected(this.result)
        })
      }
    }
    func(resolve, reject)
  }

  // then方法
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
    onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }

    const p2 = new DIYPromise((resolve, reject) => {
      if (this.state === FULFILLED) {
        runAsynctask(() => {
          try {
            const x = onFulfilled(this.result)
            // 1.处理返回Promise
            if (x instanceof DIYPromise) {
              // console.log('DIYPromise实例')
              // 2. 调用then方法
              // x.then(res => console.log(res), err => console.log(err))
              x.then(res => resolve(res), err => reject(err))
            } else {
              resolve(x)
            }
          } catch (error) {
            reject(error)
          }
        })
      }
      else if (this.state === REJECTED) {
        runAsynctask(() => {
          onRejected(this.result)
        })
      }
      else if (this.state === PENDING) {
        this.#handlers.push({
          onFulfilled: () => {
            runAsynctask(() => {
              onFulfilled(this.result)
            })
          }, onRejected: () => {
            runAsynctask(() => {
              onRejected(this.result)
            })
          }
        })
      }
    })
    return p2
  }
}
总结

手写Promise-链式编程-fulfilled状态-返回Promise

  1. 判断onFulfilled的执行结果是否为DIYPromise实例
  2. 如果是的话调用返回值的then方法,获取兑现和拒绝的原因并通过resolvereject传递即可
1.5.3 fulfilled状态-处理重复引用
需求
  1. then方法的回调函数中返回的Promise实例报错
  2. 注:下列代码中的p2
    在这里插入图片描述
const p = new DIYPromise((resolve, reject) => {
  resolve(1)
})
const p2 = p.then(res => {
  return p2
})
p2.then(
  res => { },
  err => console.log('err:', err))
核心步骤
  1. 判断是否相同,抛出异常
// 执行异步任务
function runAsynctask(callback) {
  if (typeof queueMicrotask === 'function') {
    queueMicrotask(callback)
  } else if (typeof MutationObserver === 'function') {
    const obs = new MutationObserver(callback)
    const divNode = document.createElement('div')
    obs.observe(divNode, { childList: true })
    divNode.innerText = 'itheima666'
  } else {
    setTimeout(callback, 0)
  }
}

/**
 * 链式编程-处理Promise(fulfilled状态)
*/
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class DIYPromise {
  // 状态
  state = PENDING
  // 原因
  result = undefined
  // 回调函数数组
  #handlers = [] // [{onFulfilled,onRejected}...]

  // 构造函数
  constructor(func) {
    // pending->fulfilled
    const resolve = (result) => {
      if (this.state === PENDING) {
        this.state = FULFILLED
        this.result = result
        this.#handlers.forEach(({ onFulfilled }) => {
          onFulfilled(this.result)
        })
      }
    }
    // pending->rejected
    const reject = (result) => {
      if (this.state === PENDING) {
        this.state = REJECTED
        this.result = result
        this.#handlers.forEach(({ onRejected }) => {
          onRejected(this.result)
        })
      }
    }
    func(resolve, reject)
  }

  // then方法
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
    onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }

    const p2 = new DIYPromise((resolve, reject) => {
      if (this.state === FULFILLED) {
        runAsynctask(() => {
          try {
            const x = onFulfilled(this.result)
            // 1. 处理重复引用
            if (x === p2) {
              // console.log('返回了p2')
              // 2. 抛出错误 Chaining cycle detected for promise #
              throw new TypeError('Chaining cycle detected for promise #')
            }
            if (x instanceof DIYPromise) {
              x.then(res => resolve(res), err => reject(err))
            } else {
              resolve(x)
            }
          } catch (error) {
            reject(error)
          }
        })
      }
      else if (this.state === REJECTED) {
        runAsynctask(() => {
          onRejected(this.result)
        })
      }
      else if (this.state === PENDING) {
        this.#handlers.push({
          onFulfilled: () => {
            runAsynctask(() => {
              onFulfilled(this.result)
            })
          }, onRejected: () => {
            runAsynctask(() => {
              onRejected(this.result)
            })
          }
        })
      }
    })
    return p2
  }
}
总结

手写Promise-链式编程-fulfilled状态-重复引用

  1. 判断onFulfilled函数的返回值是否和then方法内部返回的DIYPromise相同
  2. 如果相同抛出错误new TypeError('Chaining cycle detected for promise #')
1.5.4 rejected状态
需求
  1. then的第二个回调函数,执行reject时的链式编程
const p = new DIYPromise((resolve, reject) => {
  reject(1)
})
const p2 = p.then(undefined, err => {
  throw 'error'
  // return p2
  // return 2
  // return new DIYPromise((resolve, reject) => {
  //   resolve('DIYPromise-2')
  // })
})
p2.then(res => {
  console.log('p2-res:', res)
}, err => {
  console.log('p2-err:', err)
})
核心步骤
  1. 处理异常:onRejected的异常
  2. 获取返回值:onRejected的返回值
  3. fulfilled状态中的处理逻辑抽取为函数resolvePromise并复用
  4. fulfilledrejected状态中调用函数resolvePromise
// 执行异步任务
function runAsynctask(callback) {
  if (typeof queueMicrotask === 'function') {
    queueMicrotask(callback)
  } else if (typeof MutationObserver === 'function') {
    const obs = new MutationObserver(callback)
    const divNode = document.createElement('div')
    obs.observe(divNode, { childList: true })
    divNode.innerText = 'itheima666'
  } else {
    setTimeout(callback, 0)
  }
}

/**
 * 链式编程-处理Promise(fulfilled状态)
*/
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class DIYPromise {
  // 状态
  state = PENDING
  // 原因
  result = undefined
  // 回调函数数组
  #handlers = [] // [{onFulfilled,onRejected}...]

  // 构造函数
  constructor(func) {
    // pending->fulfilled
    const resolve = (result) => {
      if (this.state === PENDING) {
        this.state = FULFILLED
        this.result = result
        this.#handlers.forEach(({ onFulfilled }) => {
          onFulfilled(this.result)
        })
      }
    }
    // pending->rejected
    const reject = (result) => {
      if (this.state === PENDING) {
        this.state = REJECTED
        this.result = result
        this.#handlers.forEach(({ onRejected }) => {
          onRejected(this.result)
        })
      }
    }
    func(resolve, reject)
  }

  // then方法
  // 1. 处理异常
  // 2. 获取返回值
  // 3. 抽取函数
  // 4. 调用函数
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
    onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }

    const p2 = new HMPromise((resolve, reject) => {
      if (this.state === FULFILLED) {
        runAsynctask(() => {
          try {
            const x = onFulfilled(this.result)
            // 4. 调用函数
            resolvePromise(p2, x, resolve, reject)
            // if (x === p2) {
            //   throw new TypeError('Chaining cycle detected for promise #')
            // }
            // if (x instanceof DIYPromise) {
            //   x.then(res => resolve(res), err => reject(err))
            // } else {
            //   resolve(x)
            // }
          } catch (error) {
            reject(error)
          }
        })
      }

      else if (this.state === REJECTED) {
        runAsynctask(() => {
          // 1. 处理异常
          try {
            // 2. 获取返回值
            const x = onRejected(this.result)
            // console.log('x:', x)
            // 4. 调用函数
            resolvePromise(p2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        })
      }

      else if (this.state === PENDING) {
        this.#handlers.push({
          onFulfilled: () => {
            runAsynctask(() => {
              onFulfilled(this.result)
            })
          }, onRejected: () => {
            runAsynctask(() => {
              onRejected(this.result)
            })
          }
        })
      }
    })

    return p2
  }
}

// 3. 抽取函数
function resolvePromise(p2, x, resolve, reject) {
  if (x === p2) {
    throw new TypeError('Chaining cycle detected for promise #')
  }
  if (x instanceof DIYPromise) {
    x.then(res => resolve(res), err => reject(err))
  } else {
    resolve(x)
  }
}
总结

手写Promise-链式编程-rejected状态

  1. 判断onRejected可能出现的异常,如果出现通过reject传递
  2. 获取onRejected函数的执行结果
  3. fulfilled状态时的处理逻辑抽取为函数,rejected状态时调用函数复用逻辑
1.5.5 pending状态
需求
  1. 执行异步操作时,支持链式编程
const p = new DIYPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 2000)
})
const p2 = p.then(res => {
  throw 'error'
  // return p2
  // return 2
  // return new HMPromise((resolve, reject) => {
  //   resolve('resolve-2')
  //   // reject('reject-2')
  // })
})
p2.then(res => {
  console.log('p2-res:', res)
}, err => {
  console.log('p2-err:', err)
})
核心步骤
  1. 处理异常:
    1. pending状态时推入回调函数数组时增加try-catch
  2. 获取返回值:
    1. 推入数组时,增加获取返回值的操作
  3. 调用上一节封装的函数resolvePromise
// 执行异步任务
function runAsynctask(callback) {
  if (typeof queueMicrotask === 'function') {
    queueMicrotask(callback)
  } else if (typeof MutationObserver === 'function') {
    const obs = new MutationObserver(callback)
    const divNode = document.createElement('div')
    obs.observe(divNode, { childList: true })
    divNode.innerText = 'itheima666'
  } else {
    setTimeout(callback, 0)
  }
}

function resolvePromise(p2, x, resolve, reject) {
  if (x === p2) {
    throw new TypeError('Chaining cycle detected for promise #')
  }
  if (x instanceof DIYPromise) {
    x.then(res => resolve(res), err => reject(err))
  } else {
    resolve(x)
  }
}

/**
 * 链式编程-处理Promise(fulfilled状态)
*/
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class DIYPromise {
  // 状态
  state = PENDING
  // 原因
  result = undefined
  // 回调函数数组
  #handlers = [] // [{onFulfilled,onRejected}...]

  // 构造函数
  constructor(func) {
    // pending->fulfilled
    const resolve = (result) => {
      if (this.state === PENDING) {
        this.state = FULFILLED
        this.result = result
        this.#handlers.forEach(({ onFulfilled }) => {
          onFulfilled(this.result)
        })
      }
    }
    // pending->rejected
    const reject = (result) => {
      if (this.state === PENDING) {
        this.state = REJECTED
        this.result = result
        this.#handlers.forEach(({ onRejected }) => {
          onRejected(this.result)
        })
      }
    }
    func(resolve, reject)
  }

  // then方法
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
    onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }

    const p2 = new DIYPromise((resolve, reject) => {
      if (this.state === FULFILLED) {
        runAsynctask(() => {
          try {
            const x = onFulfilled(this.result)
            resolvePromise(p2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        })
      }

      else if (this.state === REJECTED) {
        runAsynctask(() => {
          try {
            const x = onRejected(this.result)
            resolvePromise(p2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        })
      }

      else if (this.state === PENDING) {
        this.#handlers.push({
          onFulfilled: () => {
            runAsynctask(() => {
              // 1. 处理异常
              try {
                // 2.获取返回值
                const x = onFulfilled(this.result)
                // 3.调用函数
                resolvePromise(p2, x, resolve, reject)
              } catch (error) {
                reject(error)
              }
            })
          }, onRejected: () => {
            runAsynctask(() => {
              // 1. 处理异常
              try {
                // 2.获取返回值
                const x = onRejected(this.result)
                // 3.调用函数
                resolvePromise(p2, x, resolve, reject)
              } catch (error) {
                reject(error)
              }
            })
          }
        })
      }
    })

    return p2
  }
}
总结

手写Promise-链式编程-pending状态

  1. then方法中pending状态时推入数组的函数增加try-catch捕获异常
  2. 获取推入数组的回调函数的返回值
  3. 调用上一节封装的函数并传入获取的值

小结

到目前已经将核心功能全部实现啦,接下来开始实现后续功能

  • 实现Promise的核心功能:
  • Promise:
    1. 实例方法:
      • catch
      • finally
    2. 静态方法:
      • resolve
      • reject
      • race
      • all
      • allSettled
      • any
  • Promise\A+标准,并跑通872个单元测试

2. 手写Promise实例及静态方法

2.1 实例方法-catch

需求
  1. 实现实例方法catch,可以实现如下调用
const p = new DIYPromise((resolve, reject) => {
  reject('reject-error')
  // throw 'throw-error'
})
p.then(res => {
  console.log('res:', res)
}).catch(err => {
  console.log('err:', err)
})
核心步骤
  1. 参考文档,catch等同于:then(undefined,onRjected)
  2. 直接添加catch方法,内部调用then
  3. 使用try-catch包裹constructor中的func捕获异常
function runAsynctask(callback) {
  if (typeof queueMicrotask === 'function') {
    queueMicrotask(callback)
  } else if (typeof MutationObserver === 'function') {
    const obs = new MutationObserver(callback)
    const divNode = document.createElement('div')
    obs.observe(divNode, { childList: true })
    divNode.innerText = 'itheima666'
  } else {
    setTimeout(callback, 0)
  }
}

function resolvePromise(p2, x, resolve, reject) {
  if (x === p2) {
    throw new TypeError('Chaining cycle detected for promise #')
  }
  if (x instanceof DIYPromise) {
    x.then(res => resolve(res), err => reject(err))
  } else {
    resolve(x)
  }
}

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class DIYPromise {
  // 状态
  state = PENDING
  // 原因
  result = undefined
  // 回调函数数组
  #handlers = [] // [{onFulfilled,onRejected}...]

  // 构造函数
  constructor(func) {
    // pending->fulfilled
    const resolve = (result) => {
      if (this.state === PENDING) {
        this.state = FULFILLED
        this.result = result
        this.#handlers.forEach(({ onFulfilled }) => {
          onFulfilled(this.result)
        })
      }
    }

    // pending->rejected
    const reject = (result) => {
      if (this.state === PENDING) {
        this.state = REJECTED
        this.result = result
        this.#handlers.forEach(({ onRejected }) => {
          onRejected(this.result)
        })
      }
    }

    // 2. 处理异常
    try {
      func(resolve, reject)
    } catch (error) {
      // console.log('error:', error)
      reject(error)
    }
  }

  // then方法
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
    onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }

    const p2 = new DIYPromise((resolve, reject) => {
      if (this.state === FULFILLED) {
        runAsynctask(() => {
          try {
            const x = onFulfilled(this.result)
            resolvePromise(p2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        })
      }

      else if (this.state === REJECTED) {
        runAsynctask(() => {
          try {
            const x = onRejected(this.result)
            resolvePromise(p2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        })
      }

      else if (this.state === PENDING) {
        this.#handlers.push({
          onFulfilled: () => {
            runAsynctask(() => {
              try {
                const x = onFulfilled(this.result)
                resolvePromise(p2, x, resolve, reject)
              } catch (error) {
                reject(error)
              }
            })
          }, onRejected: () => {
            runAsynctask(() => {
              try {
                const x = onRejected(this.result)
                resolvePromise(p2, x, resolve, reject)
              } catch (error) {
                reject(error)
              }
            })
          }
        })
      }
    })

    return p2
  }

  /**
   * catch方法
   * 1. 内部调用then方法
   * 2. 处理异常
   * */
  catch(onRejected) {
    // 1. 内部调用then方法
    return this.then(undefined, onRejected)
  }
}
总结

手写Promise-实例方法catch

  1. 定义catch方法,接收拒绝的回调函数onRejected
  2. catch方法的本质是内部调用then方法
  3. 调用形式为第一个回调函数传入undefined,第二个回调函数传入onRejected即可

2.2 实例方法-finally

需求
  1. 无论成功失败都会执行finally的回调函数
  2. 回调函数不接受任何参数
const p = new DIYPromise((resolve, reject) => {
  // resolve('resolve-res')
  // reject('reject-error')
  // throw 'throw-error'
})
p.then(res => {
  console.log('res:', res)
}).catch(err => {
  console.log('err:', err)
}).finally(() => {
  console.log('finally')
})
核心步骤
  1. 参考文档:finally方法类似于调用then(onFinally,onFinally),且不接受任何回调函数
  2. 注意:
    1. 版面问题,这里只保留函数部分的逻辑,其他代码未改动
    2. 后续未特殊说明,只保留函数部分代码
finally(onFinally) {
    return this.then(onFinally,onFinally)
}
总结

手写Promise-实例方法finally

  1. 添加finally方法,接收最终执行的回调函数onFinally
  2. finally方法的本质为内部调用then方法
  3. 调用形式为第一个和第二个回调函数均传入onFinally即可
finally(onFinally) {
    return this.then(onFinally,onFinally)
}

小结

到目前已经将实例方法都实现啦

  • 实现Promise的核心功能:
  • Promise:
    1. 实例方法:
      • catch
      • finally
    2. 静态方法:
      • resolve
      • reject
      • race
      • all
      • allSettled
      • any
  • Promise\A+标准,并跑通872个单元测试

2.3 静态方法-resolve

需求
  1. 返回一个带有成功原因的Promise对象
DIYPromise.resolve(new DIYPromise((resolve, reject) => {
  // resolve('resolve')
  // reject('reject')
  // throw 'error'
})).then(res => {
  console.log('res:', res)
}, err => {
  console.log('err:', err)
})
DIYPromise.resolve('javascript').then(res => {
  console.log(res)
})
核心步骤
  1. 增加静态方法resolve,根据传入的值返回不同的结果即可
  static resolve(value) {
    // 1. 判断传入值
    if (value instanceof DIYPromise) {
      // 2.1 Promise直接返回
      return value
    }
    // 2.2 转为Promise并返回(fulfilled状态)
    // return new HMPromise((resolve, reject) => {
    return new DIYPromise((resolve) => {
      resolve(value)
    })
  }
总结

手写Promise-静态方法resolve

  1. 通过static关键字添加静态方法resolve,接收参数value
  2. 内部判断传入的值
    1. 如果是Promise实例,直接返回
    2. 其他的值,创建Promise实例并返回,内部通过resolve(value)传递value

2.4 静态方法-reject

需求
  1. 返回一个带有拒绝原因的Promise对象
DIYPromise.reject('error').catch(res => {
  console.log(res)
})
核心步骤
  1. 添加静态方法
  2. 返回rejected状态的Promise
  static reject(value) {
    // 1. 返回rejected状态的Promise
    // new HMPromise((resolve,reject)=>{
    return new DIYPromise((undefined, reject) => {
      reject(value)
    })
  }
总结

手写Promise-静态方法reject

  1. 添加静态方法reject并接收参数value
  2. 内部返回一个拒绝状态的Promise实例即可

2.5 静态方法-race

需求
  1. 接收Promise数组,数组中第一个Promise敲定时,获取成功/失败结果
  2. 传入的参数不是数组,直接报错
    在这里插入图片描述
const p1 = new DIYPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 2000)
})
const p2 = new DIYPromise((resolve, reject) => {
  setTimeout(() => {
    reject(2)
  }, 1000)
})
DIYPromise.race([p1, p2, 'javascript']).then((res) => {
  console.log('res:', res)
}, err => {
  console.log('err:', err)
})
核心步骤
  1. 返回Promise
  2. 判断是否未数组,不是直接报错
  3. 等待第一个敲定
  static race(promises) {
    // 1. 返回Promise
    return new DIYPromise((resolve, reject) => {
      // 2. 判断是否为数组
      if (!Array.isArray(promises)) {
        return reject(new TypeError('Argument is not iterable'))
      }
      // 3. 等待第一个敲定
      promises.forEach(p => {
        // p.then
        DIYPromise.resolve(p).then(res => { resolve(res) }, err => { reject(err) })
      })
    })
  }
总结

手写Promise-静态方法race

  1. 添加静态方法race接收参数promises
  2. 内部返回一个新的Promise实例,在返回的Promise实例中:
    1. 判断参数是否为数组,不是通过reject传递错误
    2. 遍历Promise数组,通过resolve静态方法等待每一个兑现
    3. 任何一个兑现,调用resolve传递兑现结果
    4. 任何一个拒绝,调用reject传递拒绝原因

2.6 静态方法-all

需求
  1. 接收Promise数组
    1. 所有Promise都成功时,返回一个成功的Promise对象及成功数组
    2. 任何一个Promise失败,返回一个失败的Promise对象及第一个失败原因
const p1 = DIYPromise.resolve(1)
const p2 = new DIYPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(2)
    // reject('error')
  }, 1000)
})
const p3 = 3
DIYPromise.all([p1, p2, p3]).then(res => {
  console.log('res:', res)
}, err => {
  console.log('err:', err)
})
核心步骤
  1. 返回Promise
  2. 判断参数是否为数组:
    1. 不是:直接报错
  3. 是:
    1. 空数组直接兑现
    2. 处理全部兑现:记录结果->判断全部兑现
      3. 处理第一个拒绝
static all(promises) {
    // 1. 返回Promise实例
    return new DIYPromise((resolve, reject) => {
      // 2. 判断是否为数组
      if (!Array.isArray(promises)) {
        return reject(new TypeError('Argument is not iterable'))
      }
      // 3. 空数组直接兑现
      promises.length === 0 && resolve(promises)
      // 4.1 记录结果
      const results = []
      let count = 0
      promises.forEach((p, index) => {
        DIYPromise.resolve(p).then(res => {
          // results.push 无法保证 结果的顺序和Promise数组的顺序一致
          // index 和 Promise实例的索引一致,保证顺序
          results[index] = res
          // 4.2 判断全部兑现
          count++
          count === promises.length && resolve(results)
        }, err => {
          // 5. 处理第一个拒绝
          reject(err)
        })
      })
    })
  }
总结

手写Promise-静态方法all

  1. 添加静态方法all
  2. 内部返回Promise实例,在返回的Promise实例中:
    1. 判断参数是否为数组,不是通过reject传递错误
    2. 空数组直接以空数组为结果进行兑现
    3. 遍历Promise数组,通过resolve静态方法等待结果
      1. 处理全部兑现:
        1. 通过数组记录结果,用索引的方式来添加,目的是保证结果的顺序和Promise数组的顺序一致
        2. 通过兑现次数进行判断,因为是通过索引的方式记录结果,如果第一次兑现的是最后一个,那么数组的长度就已经和Promise数组的长度一致了,所以需要通过兑现次数来进行判断
      2. 任意一个拒绝,调用reject传递拒绝原因

2.7 静态方法-allSettled

需求-传送门
  1. 传入Promise数组,当所有对象都已敲定时
  2. 返回一个新的Promise对象及以数组形式保存的结果
const p1 = DIYPromise.resolve(1)
const p2 = 2
const p3 = new DIYPromise((resolve, reject) => {
  setTimeout(() => {
    reject(3)
  }, 1000)
})
DIYPromise.allSettled([p1, p2, p3]).then(res => {
  console.log('res:', res)
}, err => {
  console.log('err:', err)
})
核心步骤
  1. 返回Promise
  2. 判断是否为数组:
    1. 不是:报错
    2. 是:
      1. 空数组:直接兑现
      2. 等待全部敲定:并记录结果
        1. 处理兑现:{state:FULFILLED,value:'xxx'}
        2. 处理拒绝:{state:REJECTED,reason:'xxx'}
  static allSettled(promises) {
    // 1. 返回Promise
    return new DIYPromise((resolve, reject) => {
      // 2. 数组判断
      if (!Array.isArray(promises)) {
        return reject(new TypeError('Argument is not iterable'))
      }
      // 3. 为空直接敲定
      promises.length === 0 && resolve(promises)

      // 4. 等待全部敲定
      // 4.1 记录结果
      const results = []
      let count = 0
      promises.forEach((p, index) => {
        DIYPromise.resolve(p).then(res => {
          // 4.2 处理兑现{status:'fulfilled',value:''}
          results[index] = { status: FULFILLED, value: res }
          count++
          count === promises.length && resolve(results)
        }, err => {
          // 4.3 处理拒绝{status:'rejected',reason:''}
          results[index] = { status: REJECTED, reason: err }
          count++
          count === promises.length && resolve(results)
        })
      })
    })
  }
总结

手写Promise-静态方法allSettled
做法和all方法类似,区别是要获取全部敲定的结果(成功/拒绝),以及获取的结果是对象形式

  1. 添加静态方法allSettled
  2. 内部返回Promise实例,在返回的Promise实例中:
    1. 判断参数是否为数组,不是通过reject传递错误
    2. 空数组直接以空数组为结果进行兑现
  3. 遍历Promise数组,通过resolve静态方法等待敲定结果
  4. 等待全部敲定:并记录结果,根据兑现和拒绝将如下格式的内容通过索引的方式的记录到数组中
    1. 处理兑现:{state:FULFILLED,value:'xxx'}
    2. 处理拒绝:{state:REJECTED,reason:'xxx'}
  5. 根据敲定的次数判断是否全部敲定,全部敲定之后,通过resolve传递结果数组

2.8 静态方法-any

需求-传送门
  1. 传入Promise数组,
    1. 任何一个Promise对象兑现时,返回一个新的Promise对象,及对应的结果
    2. 所有Promise都被拒绝时,返回一个包含所有拒绝原因的AggregateError错误数组
const p1 = new DIYPromise((resolve, reject) => {
  setTimeout(() => {
    reject(1)
  }, 2000)
})
const p2 = 2
const p3 = new DIYPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(3)
    // reject(3)
  }, 1000)
})
DIYPromise.any([p1, p2, p3]).then(res => {
  console.log('res:', res)
}, err => {
  console.dir(err)
})
核心步骤
  1. 返回Promise
  2. 判断是否为数组
    1. 不是:报错
    2. 是:
      1. 空数组:直接拒绝
      2. 等待结果:
        1. 第一个兑现
        2. 全部拒绝
  static any(promises) {
    // 1. 返回Promise,数组判断
    return new DIYPromise((resolve, reject) => {
      if (!Array.isArray(promises)) {
        return reject(new TypeError('Argument is not iterable'))
      }
      // 2. 空数组直接拒绝
      promises.length === 0 && reject(new AggregateError(promises, 'All promises were rejected'))

      // 3. 等待结果
      const errors = []
      let count = 0
      promises.forEach((p, index) => {
        DIYPromise.resolve(p).then(res => {
          // 3.1 第一个兑现
          resolve(res)
        }, err => {
          // 3.2 全部拒绝
          errors[index] = err
          count++
          count === promises.length && reject(new AggregateError(errors, 'All promises were rejected'))
        })
      })
    })
  }
总结

手写Promise-静态方法any
做法和all方法也有点类似,区别是获取第一个兑现,或者是全部拒绝

  1. 添加静态方法any
  2. 内部返回Promise实例,在返回的Promise实例中:
    1. 判断参数是否为数组,不是通过reject传递错误
    2. 空数组直接以空数组为结果进行拒绝
  3. 遍历Promise数组,通过resolve静态方法等待结果
    1. 第一个兑现,通过resolve传递兑现结果
    2. 全部拒绝:
      1. 定义数组,保存拒绝原因,通过索引记录,目的是保证顺序和Promise数组一致
      2. 通过次数判断是否全部拒绝,当全部拒绝时,通过reject传递AggregateError类型的错误,并将拒绝原因数组传递进去即可

小结

到目前已经将静态方法都实现啦

  • 实现Promise的核心功能:
  • Promise:
    1. 实例方法:
      • catch
      • finally
    2. 静态方法:
      • resolve
      • reject
      • race
      • all
      • allSettled
      • any
  • Promise\A+标准,并跑通872个单元测试

3. 进行Promise/A+测试

接下来咱们来测试一下手写Promise的代码能否通过Promise\A+测试。

3.1 Promise\A+规范

Promise\A+是社区推出的规范,其实最早Promise也是社区推出并实现的,旨在规范Promise的实现,里面约定了:

  1. 状态必须是:pending,fulfilled,rejected
  2. then方法的详细实现细节

  3. 早期使用Promise可能需要导入一些库,比如:
  • Q
  • when
  • WinJS
  • RSVP.js

    现在已经不需要了,因为在ES6中已经加入语言标准,我们可以直接使用了:从 Chrome 32、Opera 19、Firefox 29、Safari 8 和 Microsoft Edge 开始,promises 是默认开启的。若要使缺乏完整 promise 实现的浏览器符合规范,或将 promise 添加到其他浏览器和 Node.js 中,请查看polyfill (2k gzipped)。
    ​上面提到的库,以及ES6中实现的Promise,还有我们手写的Promise其实都是按照标准进行编写的,那么如何测试是否符合标准呢?

3.2 Promise\A+测试

社区提供了promises-aplus-tests用来测试实现的Promise是否符合规范,使用方式为:

3.2.1 使用CommonJS暴露对象
 1.  提供deferred方法,返回对象{promise,resolve,reject}
	1.1  promise: pending状态的promise实例(自己手写的Promise)
	1.2  resolve:  以传入的原因兑现promise
	1.3  reject:    以传入的原因拒绝promise
   // 将我们自己手写的Promise拷贝到一个单独的文件,并在底部加上
   module.exports = {
     deferred() {
       const res = {}
       // 自己手写的Promise
       res.promise = new DIYPromise((resolve, reject) => {
         // 内部将resolve和reject赋值上去   
         res.resolve = resolve
         res.reject = reject
       })
       return res
     }
   } 
3.2.2 下包
  1. 初始化项目: npm init -y
  2. 下包:npm i promises-aplus-tests -D
3.2.3 配置并执行命令
  1. package.jsonscripts中加入
  2. 注: DIYPromise是文件名,根据实际情况调整自己的文件名即可
"test": "promises-aplus-tests DIYPromise"
  1. 执行命令:npm run test
3.2.4 执行测试

我们目前的写法中,没有考虑所有的边界情况,测试时会在2.3.3开始出错
JS进阶7 -- 手写Promise及其API_第1张图片
只需要将resolvePromise函数替换为如下写法即可:

  1. 函数名,参数顺序和原函数一致
  2. 函数内部使用,序号+说明的方式对Promise\A+的标准进行标注
    大伙可以参考注释对比确认还需要考虑哪些边界情况,
// 符合Promise\A规范(考虑了各种边界情况)
function resolvePromise(p2, x, resolve, reject) {
  // 2.3.3.1 如果p2和x引用同一个对象,通过TypeError作为原因来拒绝pormise
  if (x === p2) {
    throw new TypeError('Chaining cycle detected for promise');
  }

  /**
   * 2.3.3.2 如果x是一个promise,采用他的状态
   *  2.3.3.3.1 如果x是pengding状态,promise必须保持等待状态,直到x被fulfilled或rejected
   *  2.3.3.3.2 如果x是fulfilled状态,用相同的原因解决promise
   *  2.3.3.3.3 如果x是rejected状态,用相同的原因拒绝promise
   * */
  if (x instanceof DIYPromise) {
    x.then(y => {
      resolvePromise(p2, y, resolve, reject)
    }, reject);
  }
  // 2.3.3 如果x是一个对象或者函数
  else if (x !== null && ((typeof x === 'object' || (typeof x === 'function')))) {
    // 2.3.3.1 让then成为x.then
    try {
      var then = x.then;
    } catch (e) {
      // 2.3.3.2 如果检索属性x.then抛出了异常e,用e作为原因拒绝promise
      return reject(e);
    }

    /**
     * 2.3.3.3  如果then是一个函数,通过call调用他,并且将x作为他的this(参数1)
     * 调用then时传入2个回调函数:
     *    第一个参数叫做resolvePromise(对应到的参数2)
     *    第二个参数叫做rejectPromise(对应到参数3)
     * */

    if (typeof then === 'function') {
      // 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 均被调用,或者同一参数被调用了多次,只采用第一次调用,后续的调用会被忽略(观察called后续的赋值+判断)
      let called = false;
      try {
        then.call(
          x,
          // 2.3.3.3.1 如果 resolvePromise 以 成功原因 y 为参数被调用,继续执行 resolvePromise
          y => {
            if (called) return;
            called = true;
            resolvePromise(p2, y, resolve, reject);
          },
          // 2.3.3.3.2 如果 rejectPromise 以拒绝原因 r 为参数被调用,用 r 拒绝 promise
          r => {
            if (called) return;
            called = true;
            reject(r);
          }
        )
      }
      // 2.3.3.3.4 如果调用then抛出异常
      catch (e) {
        // 2.3.3.3.4.1 如果resolvePromise或rejectPromise已经被调用,忽略它
        if (called) return;
        called = true;

        // 2.3.3.3.4.2 否则以 e 作为拒绝原因 拒绝promise
        reject(e);
      }
    } else {
      // 2.3.3.4 如果then不是函数,用 x 作为原因 兑现promise
      resolve(x);
    }
  } else {
    // 2.3.4 如果x不是对象或函数,用 x 作为原因 兑现promise
    return resolve(x);
  }
}

替换完毕之后,再次执行npm run test,全部测试通过.
JS进阶7 -- 手写Promise及其API_第2张图片

总结

手写Promise-Promise\A+测试
PromisePromise\A+规范的关系

  1. Promise\A+是社区推出的规范,最早Promise也是社区推出并实现的,旨在规范Promise的实现,里面约定了:

    1. 状态必须是pending,fulfilled,rejected
    2. then方法的详细实现细节
    3. 各种边界情况…
  2. 早期使用Promise需要导入第三方库,现在在新式浏览器中已经不需要导入第三方库,因为Promise是默认开启的。

  3. 无论是早期实现Promise的第三方库,还是现在的新式浏览器内置的Promise,都是符合Promise\A+规范要求的。

参考资料

  1. MDN-Promise

你可能感兴趣的:(JS进阶,javascript,前端,chrome)