手写 promise 并实现 Promises/A+ 测试

手写Promise

步骤

  1. 编写 构造函数
    1. 定义类、构造器、内部 resolvereject
    2. 传递 resolvereject 给构造器参数
  2. 定义 状态和原因
    1. 用常量标记 state 的值
    2. 定义 stateresult
    3. resolverejected 中修改 stateresult
  3. 实现 then方法
    1. 定义 then 方法,参数列表为 onFulFilledonRejected
    2. 判断参数类型,进行修改
    3. 根据 state 判定执行哪个回调
      • 如果是 pending,将 onFulFilledonRejected 存入 #handle(私有属性,用来存放调用 resolve/rejected 之前,then 方法的参数列表),然后在构造函数的 resolvereject 中遍历 #handle 提取对应的回调
      • 如果是 fulfilled,执行 onFulfilled
      • 如果是 rejected, 执行 onRejected
  4. 异步任务
    1. 选择可以用来实现异步的方法 queueMicrotaskMutationObserversetTimeout
    2. 封装实现异步的方法
    3. 传递回调函数,需要使用指数函数进行包裹,不然会报错
  5. 链式编程
    1. then 方法返回一个 MyPromise 对象
    2. 返回值
      1. 返回正常值,将值给 resolve 或者 reject
      2. 抛出错误,调用 reject
      3. 返回了返回值本身(重复引用),判断是否相等,相等则抛出错误
    3. 抽取步骤三作为一个函数,重复引用
  6. 实例方法
    1. catch(onRejected) : 捕获错误,如果 then 没有捕获到的话,此方法是 Promise.prototype.then(undefined, onRejected) 的一种简写形式
      • 实现: 直接返回 this.then(undefined, onRejected)
    2. finally() : Promise 实例的 finally() 方法用于注册一个在 promise 敲定(兑现或拒绝)时 调用的函数。它会立即返回一个等效的 Promise 对象,这可以允许你链式调用其他 promise 方法
      • 实现: 直接返回this.then(onFinally, onFinally)
  7. 静态方法(实现参考文档描述)
    1. resolve(value) :
      • 功能:Promise.resolve() 静态方法将给定的值转换为一个 Promise 。如果该值本身就是一个 Promise,那么该 Promise 将被返回;如果该值是一个 thenable 对象,Promise.resolve() 将调用其 then() 方法及其两个回调函数;否则,返回的 Promise 将会以该值兑现
      • 实现:(判断参数类型)
        • 如果传入的 value 的类型是 MyPromise 则直接返回value,
        • 否则实例化一个MyPromise对象,调用 resolve ,将 value 作为参数传入
    2. reject(reason) :
      • 功能:Promise.reject() 静态方法返回一个已拒绝(rejected)的 Promise 对象,拒绝原因为给定的参数
      • 实现:(不用判断参数类型)
        • 实例化一个 MyPromise ,调用 reject ,将 reason 作为参数传入
    3. race(iterable) : 返回首个成功的,不管是 resolve 还是 reject
      • 功能:Promise.race() 静态方法接受一个 promise 可迭代对象作为输入,并返回一个 Promise。这个返回的 promise 会随着第一个 promise 的敲定而敲定
      • 实现:(判断类型)
        • 实例化一个 MyPromise
        • 如果 iterable 不是可迭代对象(判断是否是数组),则调用 reject(new TypeError('Argument is not iterable'))
        • 否则遍历 iterable (使用forEach遍历),每一个对象调用静态方法 MyPromise.resolve().then()因为无法判断每一个元素的类型,但是我们需要,如果是MyPromise的话返回 resolve/reject 的结果,如果是非 MyPromise 则返回自身值
    4. all(iterable) : 如果迭代对象有一个元素被拒绝则返回的 MyPromise 拒绝,否则返回所有成功的 resolve(results),空迭代对象,则返回 resolve(iterable)
      • 功能:Promise.all() 静态方法接受一个 Promise 可迭代对象作为输入,并返回一个Promise。当所有输入的 Promise 都被兑现时,返回的 Promise 也将被兑现(即使传入的是一个空的可迭代对象),并返回一个包含所有兑现值的数组。如果输入的任意 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因
      • 实现:(判断类型)
        • 实例化一个 MyPromise
        • iterable 不是可迭代对象(判断是否是数组),则调用 reject(new TypeError('Argument is not iterable'))
        • iterable为空,则调用 resolve(iterable)
        • iterable可迭代且不为空,遍历 iterable (使用forEach遍历),每一个对象调用静态方法 MyPromise.resolve().then(),定义返回 数组(results),记录兑现次数(count)
        • 如果兑现,将结果存入 results ,count 加一
        • 如果 count 等于迭代对象的长度 ,则兑现(调用resolve(results)),如果拒绝则拒绝(调用 reject)
    5. allSettled : 无论迭代对象的元素是兑现还是拒绝都将 结果和状态 存入result,最后兑现,参数是results
      • 功能:Promise.allSettled() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个单独的 Promise。当所有输入的 Promise 都已敲定时(包括传入空的可迭代对象时),返回的 Promise 将被兑现,并带有描述每个 Promise 结果的对象数组
      • 实现:(判断类型)
        • 实例化一个 MyPromise
        • iterable 不是可迭代对象(判断是否是数组),则调用 reject(new TypeError('Argument is not iterable'))
        • iterable为空,则调用 resolve(iterable)
        • iterable可迭代且不为空,遍历 iterable (使用forEach遍历),每一个对象调用静态方法 MyPromise.resolve().then(),定义返回 数组(results),记录兑现次数(count)
        • 如果兑现,将 { status: FULFILLED, value: res } 存入 results ,如果拒绝,将 { status: REJECTED, reason: err },count 加一
        • 如果 count 等于迭代对象的长度 ,则兑现(调用resolve(results))
    6. any : 如果迭代对象有一个元素被兑现则返回的 MyPromise 兑现,否则返回一个 AggregateError,参数是所有拒绝的原因,空迭代对象,则返回 reject(new AggregateError(promises, 'All promises were rejected'))
      • 功能:Promise.any() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个 Promise。当输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。当所有输入 Promise 都被拒绝(包括传递了空的可迭代对象)时,它会以一个包含拒绝原因数组的 AggregateError 拒绝
      • 实现:
        • 实例化一个 MyPromise
        • iterable 不是可迭代对象(判断是否是数组),则调用 reject(new TypeError('Argument is not iterable'))
        • iterable为空,则调用 reject(new AggregateError(promises, 'All promises were rejected'))
        • iterable可迭代且不为空,遍历 iterable (使用forEach遍历),每一个对象调用静态方法 MyPromise.resolve().then(),定义返回数组(errors),记录兑现次数(count)
        • 如果兑现,则兑现(调用resolve(res))
        • 如果拒绝,则将原因存入 errors
        • 如果所有都拒绝并且count 等于迭代对象的长度 ,则拒绝(调用reject(new AggregateError(errors, 'All promises were rejected')))

异步依赖api

js 复制代码
function runAsynctask(callback) {
      // console.log(typeof 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 = 'divNode'
      } else {
            setTimeout(callback, 0)
      }
}

链式编程依赖api

js 复制代码
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 MyPromise) {
            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 如果then不是对象或函数,用 x 作为原因 兑现promise
            return resolve(x);
      }
}

Promise/A+ 规范

  1. 使用 CommonJS 的模块化语法暴露出去

    1. 提供 deferred 方法,返回对象 {promise,resolve,reject}

      js 复制代码
      module.exports = {
        deferred() {
          const res = {}
          res.promise = new MyPromise((resolve, reject) => {
                res.resolve = resolve
                res.reject = reject
          })
          return res
        }
      }
    2. promise: pending 状态的 promise 实例

    3. resolve: 以传入的原因兑现 promise

    4. reject: 以传入的原因拒绝 promise

  2. 下包

    1. 初始化项目 npm init -y
    2. npm i promises-aplus-tests -D
  3. package.json 中配置

    json 复制代码
    "scripts": {
     "test": "promises-aplus-tests MyPromise"
    },
  4. 运行 npm run test

代码汇总

MyPromise.js

js 复制代码
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
      state = PENDING;
      result = undefined;

      #handlers = [];

      constructor(func) {
            const resolve = (result) => {
                  if (this.state === PENDING) {
                        this.state = FULFILLED;
                        this.result = result;
                        this.#handlers.forEach(({ onFulfilled }) => {
                              onFulfilled(this.result);
                        });
                  }
            };
            const reject = (result) => {
                  if (this.state === PENDING) {
                        this.state = REJECTED;
                        this.result = result;
                        this.#handlers.forEach(({ onRejected }) => {
                              onRejected(this.result);
                        });
                  }
            };
            try {
                  func(resolve, reject);
            } catch (error) {
                  reject(error)
            }
      }

      // 实例方法
      then(onFulfilled, onRejected) {
            // 判断类型
            onFulfilled = typeof onFulfilled === "function" ? onFulfilled : x => x
            onRejected = typeof onRejected === "function" ? onRejected : x => { throw x }
            const p2 = new MyPromise((resolve, reject) => {
                  if (this.state === FULFILLED) {
                        // 参数需要使用指数函数,不然queueMicrotask会报错
                        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(onRejected) {
            return this.then(undefined, onRejected)
      }
      finally(onFinally) {
            return this.then(onFinally, onFinally)
      }

      // 静态方法
      static resolve(value) {
            if (value instanceof MyPromise) {
                  return value
            }
            return new MyPromise(resolve => {
                  resolve(value)
            })
      }
      static reject(reason) {
            return new MyPromise((undefined, reject) => {
                  reject(reason)
            })
      }
      static race(iterable) {
            return new MyPromise((resolve, reject) => {
                  if (!Array.isArray(iterable)) {
                        reject(new TypeError("Argument is not iterable"))
                  }

                  iterable.forEach(item => {
                        MyPromise.resolve(item).then(res => { resolve(res) }, err => { reject(err) })
                  })
            })
      }
      // 手写promise.all()
      // 1. 定义,返回MyPromise,判断参数是否是数组
      // 2. 如果数组是空,返回数组
      // 3. 遍历数组,处理全部参数,
      // 4. 定义返回的数组和标记(是否所有参数都执行完毕)
      // 5. 返回的顺序需要和传入的一致,所以利用索引进行存入
      // 6. 如果有抛出错误,则直接执行reject,跳出函数
      static all(iterable) {
            return new MyPromise((resolve, reject) => {
                  if (!Array.isArray(iterable)) {
                        reject(new TypeError("Argument is not iterable"))
                  }
                  iterable.length === 0 && resolve(iterable)
                  const results = []
                  let count = 0
                  iterable.forEach((item, index) => {
                        MyPromise.resolve(item).then(res => {
                              results[index] = res
                              count++
                              count === iterable.length && resolve(results)
                        }, err => {
                              reject(err)
                        })
                  })
            })
      }

      static allSettled(iterable) {
            return new MyPromise((resolve, reject) => {
                  if (!Array.isArray(iterable)) {
                        reject(new TypeError("Argument is not iterable"))
                  }
                  iterable.length === 0 && resolve(iterable)
                  const results = []
                  let count = 0
                  iterable.forEach((item, index) => {
                        MyPromise.resolve(item).then(res => {
                              results[index] = { status: FULFILLED, value: res }
                              count++
                              count === iterable.length && resolve(results)
                        }, err => {
                              results[index] = { status: REJECTED, reason: err }
                              count++
                              count === iterable.length && resolve(results)
                        })
                  })
                  resolve(arr)
            })
      }

      static any(iterable) {
            return new MyPromise((resolve, reject) => {
                  // 如果不是数组
                  if (!Array.isArray(iterable)) {
                        reject(new TypeError("undefined is not iterable (cannot read property Symbol(Symbol.iterator))"))
                  }
                  // 如果数组为空
                  iterable.length === 0 && reject(new AggregateError(iterable, 'All promises were rejected'))
                  // 如果有一个可兑现,直接resolve
                  // 如果都被拒绝,reject
                  const results = []
                  let count = 0
                  iterable.forEach((item, index) => {
                        MyPromise.resolve(item).then(res => {
                              resolve(res)
                        }, err => {
                              results[index] = { status: REJECTED, reason: err }
                              count++
                              count === iterable.length && reject(new AggregateError(results, 'All promises were rejected'))
                        })
                  })
                  resolve(arr)
            })
      }
}

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 = "divNode";
      } else {
            setTimeout(callback, 0);
      }
}

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 MyPromise) {
            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 如果then不是对象或函数,用 x 作为原因 兑现promise
            return resolve(x);
      }
}


module.exports = {
      deferred() {
            const res = {}
            res.promise = new MyPromise((resolve, reject) => {
                  res.resolve = resolve
                  res.reject = reject
            })
            return res
      }
}
相关推荐
上趣工作室几秒前
vue2在el-dialog打开的时候使该el-dialog中的某个输入框获得焦点方法总结
前端·javascript·vue.js
家里有只小肥猫1 分钟前
el-tree 父节点隐藏
前端·javascript·vue.js
fkalis2 分钟前
【海外SRC漏洞挖掘】谷歌语法发现XSS+Waf Bypass
前端·xss
陈随易1 小时前
农村程序员-关于小孩教育的思考
前端·后端·程序员
云深时现月1 小时前
jenkins使用cli发行uni-app到h5
前端·uni-app·jenkins
昨天今天明天好多天1 小时前
【Node.js]
前端·node.js
亿牛云爬虫专家1 小时前
Puppeteer教程:使用CSS选择器点击和爬取动态数据
javascript·css·爬虫·爬虫代理·puppeteer·代理ip
2401_857610032 小时前
深入探索React合成事件(SyntheticEvent):跨浏览器的事件处理利器
前端·javascript·react.js
雾散声声慢2 小时前
前端开发中怎么把链接转为二维码并展示?
前端
熊的猫2 小时前
DOM 规范 — MutationObserver 接口
前端·javascript·chrome·webpack·前端框架·node.js·ecmascript