简单实现一个 Promise,并通过 Promise a+测试

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象。

以上出自 ECMAScript 6 入门 阮一峰

无注释简洁版代码查看

1、提前准备

  1. 创建文件夹,进入文件夹,初始化为 npm 项目,编辑 package.json 添加执行脚本用于快速运行任务,同时添加 type 属性,其值为 module

    • 执行 npm 命令

      npm init
    • 修改 package.json 完整信息如下:

      {
          "name": "my_promise",
          "type": "module",
          "scripts": {
              "dev": "node src/myPromise.js"
          }
      }
  2. 封装 isFunction 函数,用于判断传入值 value 的类型是否为函数

    /**
     * 传入值否为函数
     * @param {*} value 传入value
     * @returns boolean
     */
    const isFunction = value => typeof value === 'function'
  3. 封装 isObject 函数,判断传入值 value 的类型是否为对象

    /**
     * 传入值类型是否为对象
     * @param {*} value 传入值
     * @returns boolean
     */
    const isObject = value => Object.prototype.toString.call(value) === '[object Object]'
  4. Promise 对象状态,有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)

    // 等待
    const PENDING = 'pending'
    // 成功
    const FULFILLED = 'fulfilled'
    // 失败
    const REJECTED = 'rejected'

2、基本方法

Promise 对象构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。resolve 函数的作用是,将 Promise 对象的状态从“未完成”变为“成功”(即从 pending 变为 fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject 函数的作用是,将 Promise 对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

  1. 创建构造函数,实现 resolve 和 reject 函数的定义,在创建 Promise 的时候传入,根据调用的结果调用不同的函数

    class MyPromise {
        /**
         * 初始化函数
         * @param { Function } executor 执行函数
         */
        constructor(executor) {
            /**
             * 异步操作成功时调用
             * @param {*} value 操作成功结果
             * @returns void
             */
            const resolve = value => {}
            /**
             * 异步操作失败时调用
             * @param {*} reason 错误信息
             * @returns void
             */
            const reject = reason => {}
    
            // 执行外部传入参数
            executor(resolve, reject)
        }
    }
  2. 添加 Promise 状态和返回结果,Promise 对象的状态不受外界影响,一旦状态改变,就不会再变,任何时候都可以得到这个结果。

    class MyPromise {
        constructor(executor) {
            // 保存异步操作执行结果
            this.result = null
            // 记录当前Promise状态
            this.status = PENDING
        }
    }
  3. 修改 resolve 和 reject 函数,添加当前对象状态修改和异步执行结果保存功能

    class MyPromise {
        constructor(executor) {
            const resolve = value => {
                // 若当前状态已经被修改则不可修改状态
                if (this.state !== PENDING) return
                // 修改状态为执行成功
                this.state = FULFILLED
                // 保存操作成功结果
                this.result = value
            }
            const reject = reason => {
                if (this.state !== PENDING) return
                // 修改状态为执行失败
                this.state = REJECTED
                // 保存执行失败错误信息
                this.result = reason
            }
        }
    }

    当前完整代码点击查看

3、添加 then 方法

  1. 实现 then 方法
    then 方法的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then 方法的第一个参数是 resolved 状态的回调函数,第二个参数是 rejected 状态的回调函数,它们都是可选的

    class MyPromise {
        /**
         * 添加状态改变时的回调函数
         * @param {*} onFulfilled FULFILLED状态回调函数
         * @param {*} onRejected FULFILLED状态的回调函数
         */
        then(onFulfilled, onRejected) {}
    }
  2. 添加 callbacks 字段,初始化为空数组,若当前状态为 pending 则用于保存 then 所传入的函数

    class MyPromise {
        constructor(executor) {
        ...
            // 用于记录then函数的参数信息
            this.callbacks = []
        }
    
        then(onFulfilled, onRejected) {
            // 创建回调信息
            const callback = { onFulfilled, onRejected }
            // 当前状态为等待中
            if (this.state === PENDING) {
                // 保存回调信息到callbacks数组中
                this.callbacks.push(callback)
                return
            }
        }
    }
  3. 添加 \_runCallback 函数,若当前状态不为 pending 则 then 传入的函数,若状态为 FULFILLED 同时 onFulfilled 类型为函数,则传入 result 作为参数执行 onFulfilled 函数。若状态为 REJECTED 同时 onRejected 类型为函数,则传入 result 作为参数执行 onRejected 函数

    class MyPromise {
        then(onFulfilled, onRejected) {
            // 执行当前任务
            this._runCallback(callback)
        }
    
        /**
         * 执行then传入的函数信息
         * @param {object} callback 需要执行的回调函数信息
         * @param {*} callback.onFulfilled 成功状态下回调函数
         * @param {*} callback.onRejected 失败状态下回调函数
         */
        _runCallback({ onFulfilled, onRejected }) {
            // 成功状态
            if (this.state === FULFILLED) {
                // 若onFulfilled为函数,则传入result作为参数执行
                isFunction(onFulfilled) && onFulfilled(this.result)
            }
            // 失败状态
            if (this.state === REJECTED) {
                // 若onRejected为函数,则传入result作为参数执行
                isFunction(onRejected) && onRejected(this.result)
            }
        }
    }

    当前完整代码点击查看

4、添加异步执行功能

当 then 调用的时候,如果当前状态为 PENDING,就会保存传入参数到当前 Promise 对象中,等待 Promise 结果完成后,调用已保存的函数执行。

  1. 添加\_runAllCallbacks 函数,将已经加入到 callbacks 的函数循环调用执行

    class MyPromise {
        /**
         * 执行callbacks数组中保存的
         */
        _runAllCallbacks = () => {
            // 循环调用数组元素执行
            this.callbacks.forEach(this._runCallback)
            // 完成后清空数组
            this.callbacks = []
        }
    }
  2. 实现 try..catch 包裹 executor,若执行的时候抛出异常则调用 reject 函数

    class MyPromise {
        constructor(executor) {
            try {
                // 执行外部传入参数
                executor(resolve, reject)
            } catch (error) {
                // 若executor抛出异常则调用reject函数
                reject(error)
            }
        }
    }
  3. 完善 resolve 和 reject 函数,执行的时候如果 callbacks 不为空,则运行数组中的信息

    class MyPromise {
        constructor(executor) {
            const resolve = value => {
                // 执行数组中的函数
                this._runAllCallbacks()
            }
    
            const reject = reason => {
                // 执行数组中的函数
                this._runAllCallbacks()
            }
        }
    }
  4. 将任务添加通过 queueMicrotask 添加至微任务队列中,queueMicrotask 是一个简短的函数,它将在当前任务(task)完成其工作之后运行,并且在执行上下文的控制返回到浏览器的事件循环之前,没有其他代码等待运行

    class MyPromise {
        constructor(executor) {
            const resolve = value => {
                // 将数组中的函数在微任务队列中运行
                queueMicrotask(this._runAllCallbacks)
            }
    
            const reject = reason => {
                // 将数组中的函数在微任务队列中运行
                queueMicrotask(this._runAllCallbacks)
            }
        }
        then(onFulfilled, onRejected) {
            // 在微任务队列中执行当前任务
            queueMicrotask(() => this._runCallback(callback))
        }
    }

    当前完整代码点击查看

5、实现 resolvePromise 功能

用于处理 resolve 函数传入参数 value 的一些问题,若 resolve 传入 Promise 或者 Promise like 的函数时需要处理相关情况

  1. 若 value 为 this 的时候,抛出 TypeError,并且执行 onRejected 函数

    class MyPromise {
        constructor(executor) {
            /**
             * 处理resolve传入参数信息
             * @param {*} value resolve传入参数
             */
            const resolvePromise = value => {
                // 传入value为当前对象时,抛出异常
                if (value === this) {
                    return reject(new TypeError('Chaining cycle detected for promise'))
                }
            }
        }
    }
  2. 若 value 类型为 MyPromise 对象时候,则将当前 resolve 和 reject 传入 value then 函数之中

    class MyPromise {
        constructor(executor) {
            const resolvePromise = value => {
                ...
                // 当前value为MyPromise则执行函数,则将当前resolve和reject传入value then之中
                if (value instanceof MyPromise) {
                    return value.then(resolve, reject)
                }
            }
        }
    }
  3. 若 value 为一个对象或者函数当其中包含一个 then 函数,则执行函数

    class MyPromise {
        constructor(executor) {
            const resolvePromise = value => {
                // 当value值为对象的时候
                if (isObject(value) || isFunction(value)) {
                    try {
                        // 如果value包含一个then字段
                        const then = value.then
                        // 如果then为一个函数
                        if (isFunction(then)) {
                            // 进入一个新的MyPromise对象
                            return new MyPromise(then.bind(value)).then(resolve, reject)
                        }
                    } catch (error) {
                        // 如果抛出异常则调用reject函数
                        return reject(error)
                    }
                }
            }
        }
    }
  4. 若为其他,则修改当前状态,将 value 作为参数调用 resolve 之中

    class MyPromise {
        constructor(executor) {
            const resolvePromise = value => {
                ...
                // 若为其他类型则直接调用resolve函数
                resolve(value)
            }
    
            try {
                // 使用resolvePromise代替resolve函数
                executor(resolvePromise, reject)
            } catch (error) {
                // 若executor抛出异常则调用reject函数
                reject(error)
            }
        }
    }
  5. 多次执行 resolve/reject 函数问题处理

    1. 如果传入的为一个 Promise、MyPromise 或者其他包含 then 函数的对象是,会创建一个全新的 MyPromie 对象,在其 then 函数中执行,如若 resolve 执行两次则第一个为 thenable 对象,则会放入微任务队列中执行,导致执行第二个函数执行,所以需要添加限制保证 reslove/reject 函数只能执行一次
    /**
     * 预期输出 data: aaa
     * 实际输出 data: bbb
     */
    const p = new MyPromise((resolve, reject) => {
        resolve({
            then: resolve => resolve('aaa')
        })
        resolve('bbb')
    })
    p.then(data => {
        console.log('data: ', data)
    })
    1. 处理函数执行调用问题,通过添加 isExecuted 记录函数执行状态,若有一个函数被执行则拒绝执行其他函数
    class MyPromise {
        constructor(executor) {
            // 用于记录constructor的参数执行状态
            let isExecuted = false
            try {
                // 执行外部传入参数
                executor(
                    value => {
                        if (isExecuted) return
                        isExecuted = true
                        resolvePromise(value)
                    },
                    reason => {
                        if (isExecuted) return
                        isExecuted = true
                        reject(reason)
                    }
                )
            } catch (error) {
                if (isExecuted) return
                isExecuted = true
                // 若executor抛出异常则调用reject函数
                reject(error)
            }
        }
    }

    修改后执行结果符合预期

    /**
     * 预期输出 data: aaa
     * 实际输出 data: aaa
     */
    const p = new MyPromise((resolve, reject) => {
        resolve({
            then: resolve => resolve('aaa')
        })
        resolve('bbb')
    })
    p.then(data => {
        console.log('data: ', data)
    })
  6. 函数封装和命名修改

    1. 控制 reject 和 resolve 函数执行时,存在大量重复代码,可以使用函数将其封装

      class MyPromise {
          constructor(executor) {
              // 用于记录constructor的参数执行状态
              let isExecuted = false
      
              /**
               * 用于限制函数执行次数
               * @param {Function} fn 需要限制的函数
               * @returns 若isExecuted为true则函数不会执行
               */
              const executedHandler = fn => result => {
                  if (isExecuted) return
                  isExecuted = true
                  isFunction(fn) && fn(result)
              }
      
              try {
                  // 执行外部传入参数
                  executor(executedHandler(resolvePromise), executedHandler(reject))
              } catch (error) {
                  // 若executor抛出异常则调用reject函数
                  executedHandler(reject)(error)
              }
          }
      }
    2. resolve 和 reject 为 executor 函数中的参数,为了便于理解,将当前 resolve 函数重新命名为 onFulfilled ,将 reject 函数重命名为 onRejected

      class MyPromise {
          constructor(executor) {
              /**
               * 异步操作成功时调用
               * @param {*} value 操作成功结果
               * @returns void
               */
              const onFulfilled = value => {}
              /**
               * 异步操作失败时调用
               * @param {*} reason 错误信息
               * @returns void
               */
              const onRejected = reason => {}
      
              try {
                  // 执行外部传入参数
                  executor(executedHandler(resolvePromise), executedHandler(onRejected))
              } catch (error) {
                  // 若executor抛出异常则调用reject函数
                  executedHandler(onRejected)(error)
              }
          }
      }
    3. 因为 onFulfilled 和 onRejected 实际完成的任务相似,所以添加一个新的函数 resultHandler 通过传入参数进行生成 onFulfilled 和 onRejected

      class MyPromise {
          constructor(executor) {
              /**
               * 通过传递状态生成onFulfilled或者onRejected函数
               * @param {*} state 需要生成的函数其状态
               * @returns onFulfilled | onRejected
               */
              const resultHandler = state => result => {
                  // 若当前状态已经被修改则不可修改状态
                  if (this.state !== PENDING) return
                  // 修改状态为执行成功
                  this.state = state
                  // 保存操作成功结果
                  this.result = value
                  // 将数组中的函数在微任务队列中运行
                  queueMicrotask(this._runAllCallbacks)
              }
      
              /**
               * 异步操作成功时调用
               * @param {*} value 操作成功结果
               * @returns void
               */
              const onFulfilled = resultHandler(FULFILLED)
              /**
               * 异步操作失败时调用
               * @param {*} reason 错误信息
               * @returns void
               */
              const onRejected = resultHandler(REJECTED)
          }
      }

    当前完整代码点击查看

6、实现 then 函数链式调用

then 函数需要实现链式调用,则需要返回一个新的 Promise,将当前 then 方法中的 onFulfilled 或 onRejected 的返回值作为新 Promise 的 reslove 参数,若当前 then 函数的 onFulfilled 或 onRejected 不存在,则将上一层级 Promise 执行结果传递至新的 Promise,同时若当前 then 方法中的参数执行抛出异常,则执行下一层 Promise 的 reject 函数

  1. 修改 then 方法返回值

    class MyPromise {
        /**
         * 添加状态改变时的回调函数
         * @param {*} onFulfilled FULFILLED状态回调函数
         * @param {*} onRejected FULFILLED状态的回调函数
         */
        then(onFulfilled, onRejected) {
            return new MyPromise((resolve, reject) => {
                // 创建回调信息,将需要返回的Promise执行函数保存至callback之中
                const callback = { resolve, reject, onFulfilled, onRejected }
                // 当前状态为等待中
                if (this.state === PENDING) {
                    // 保存回调信息到callbacks数组中
                    this.callbacks.push(callback)
                    return
                }
                // 在微任务队列中执行当前任务
                queueMicrotask(() => this._runCallback(callback))
            })
        }
    }
  2. 修改 \_runCallback 执行信息,将 then 传递的参数和需要返回的 Promise 的 resolve 和 reject 根据函数执行状态处理返回 Promise 的结果

    class MyPromise {
        /**
         * 执行then传入的函数信息
         * @param {object} callback 需要执行的回调函数信息
         * @param {Function} callback.resolve then函数返回的Promise reject函数
         * @param {Function} callback.reject then函数返回的Promise reject函数
         * @param {*} callback.onFulfilled 成功状态下回调函数
         * @param {*} callback.onRejected 失败状态下回调函数
         */
        _runCallback = ({ resolve, reject, onFulfilled, onRejected }) => {
            try {
                // 成功状态
                if (this.state === FULFILLED) {
                    // 若onFulfilled为函数,则传入result作为参数执行,并作为resolve参数传递给then返回的Promise之中
                    // 若onFulfilled不为函数,直接传递当前result至返回的Promise resolve之中
                    isFunction(onFulfilled) ? resolve(onFulfilled(this.result)) : resolve(this.result)
                }
                // 失败状态
                if (this.state === REJECTED) {
                    // 若onRejected为函数,则传入result作为参数执行,并作为resolve参数传递给then返回的Promise之中
                    // 若onRejected不为函数,直接传递当前result至返回的Promise reject之中
                    isFunction(onRejected) ? resolve(onRejected(this.result)) : reject(this.result)
                }
            } catch (error) {
                // 若当前函数执行抛出,直接调用返回的Promise reject方法
                reject(error)
            }
        }
    }

    当前完整代码点击查看

7、使用 promises-aplus-tests 测试

  1. 安装 promises-aplus-tests

    npm install promises-aplus-tests -D
  2. 编写测试 adapter

    import PromiseAplusTests from 'promises-aplus-tests'
    import MyPromise from '../src/myPromiseSimple.js'
    
    const adapter = {
        resolved(value) {
            return new MyPromise((resolve, reject) => {
                resolve(value)
            })
        },
        rejected(reason) {
            return new MyPromise((resolve, reject) => {
                reject(reason)
            })
        },
        deferred() {
            let dfd = {}
            dfd.promise = new MyPromise((resolve, reject) => {
                dfd.resolve = resolve
                dfd.reject = reject
            })
            return dfd
        }
    }
    PromiseAplusTests(adapter, err => {
        console.log(err)
    })

    当前完整代码点击查看

8、TS 版本

  1. 添加工具函数类型

    enum STATE {
        PENDING,
        FULFILLED,
        REJECTED
    }
    
    type TResolveFn<T = any> = (value: T) => void
    type TRejectFn = (value: any) => void
    type TOnFulfilledFn<T = any, R = any> = (data: T) => R
    type TOnRejectedFn = (data: any) => any
    
    type TPromiseExecutor<T = unknown> = (resolve: TResolveFn<T>, reject: TRejectFn) => void
    interface ICallback {
        resolve: TResolveFn
        reject: TRejectFn
        onFulfilled?: TOnFulfilledFn
        onRejected?: TOnRejectedFn
    }
    
    const isFunction = (value: any): value is Function => typeof value === 'function'
    const isObject = (value: any): value is object => Object.prototype.toString.call(value) === '[object Object]'
  2. 实现 resolve 的类型

    export default class MyPromise<T> {
        private result: any
        private state: STATE = STATE.PENDING
        private callbacks: ICallback[] = []
    
        constructor(executor: TPromiseExecutor<T>) {
            try {
                executor(executeHandler<T>(resolvePromise), executeHandler(onRejected))
            } catch (error) {
                executeHandler(onRejected)(error)
            }
        }
    }
  3. 添加 then 方法的类型

    export default class MyPromise<T> {
        then<R>(onFulfilled?: TOnFulfilledFn<T, R>, onRejected?: TOnRejectedFn) {
            return new MyPromise<R>((resolve, reject) => {
                const task = { onFulfilled, onRejected, resolve, reject }
                if (this.state === STATE.PENDING) {
                    this.callbacks.push(task)
                    return
                }
                queueMicrotask(() => this._runTask(task))
            })
        }
    }

    当前完整代码点击查看


标签:暂无标签
版权属于:pinktu 所有,采用《知识共享署名许可协议》进行许可,转载请注明文章来源。

本文链接: https://dgzhuya.com/index.php/archives/12/

下一篇
没有了
赞 (0)

回到顶部