手写实现符合Promise/A+的Promise

76 阅读11分钟

Promise的实现

基础前置知识

  • Promise是一种用于处理异步操作的技术
  • Promise有待定(pending)、已完成(fulfilled)和已拒绝(rejected)三种状态
  • then方法可以接受完成或拒绝的数据
  • 可以链式调用

跟着感觉走

简单示例

const p1 = new Promise((resolve, reject)=>{
  if(true) setTimeout(resolve, 1000, 10)
  else setTimeout(reject, 1000, '失败了')
})
p1.then(value=>{console.log(value)}, reason=>{console.log(reason)})

示例分析

  • 首先定义一个MyPromise类 初始状态是pending
  • 接受一个executor执行函数,executor会立即执行。然后接受两个参数(resolve,reject)并由MyPromise提供用于改变MyPromise的状态。resolve执行状态变更为fulfilled,reject方法执行状态变更为rejected
  • 然后调用then方法并提供两个参数,分别对应resolve和reject的回调数据

实现

第一版:同步版本

  • 代码

    // 首先定义三种状态
    const PENDING = "pending"
    const FULFILLED = "fulfilled"
    const REJECTED = "rejected"
    
    // 定义MyPromise类
    class MyPromise {
        // 定义constructor 并接受一个executor参数
        constructor(executor){
            // 初始状态
            this.status = PENDING
            // 完成状态下的数据
            this.value = undefined
            // 拒绝状态下的数据
            this.reason = undefined
    
            // 定义resolve,reject方法用于接受对应数据和改变状态
            const resolve = value=>{
                this.value = value
                this.status = FULFILLED
            }
            const reject = reason=>{
                this.reason = reason
                this.status = REJECTED
            }
    
            // 接下来执行executor 执行异常直接抛出错误
            try {
                executor(resolve, reject)
            } catch (error) {
                reject(error)
            }
    
        }
    
        // 定义then方法用于捕获结果并接受两个参数onResolve, onReject分别对应resolve和reject的数据回调
        then(onResolve, onReject){
            if(this.status === FULFILLED) {
                onResolve(this.value)
            }
            if(this.status === REJECTED) {
                onReject(this.reason)
            }
        }
    }
    
  • 测试

    new MyPromise((reslove, reject)=>{
        reslove(2)
    }).then(value=>{
        console.log(value) // 2
    })
    
    new MyPromise((reslove, reject)=>{
        reject("我失败了")
    }).then(value=>{
        console.log(value) 
    }, reason=>{
        console.log(reason) // 我失败了
    })
    
  • 问题

    现在只是实现了同步情况下promise功能,但是promise是为了解决异步问题,所以现在要实现第二版:异步版

第二版:异步版本

  • 需要解决的问题?

    • 怎么解决异步? 在我们调用then方式时,promise状态还是pending,resolve或者reject还未执行。那么我们就可以设置一个数组来保存then方法中的回调函数,在resolve或reject执行完成后遍历去执行then方法的回调函数。

    • 为什么用数组来保存then方法?

      在一个典型的 Promise 实现中,then 方法允许注册两个回调函数:一个用于处理成功状态(onResolve),另一个用于处理失败状态(onReject)。这两个参数都是可选的,用户可以只传递其中一个或两个。

      但是,在某些情况下,确实会有多个回调函数。例如,一个 Promise 实例可能会被多次调用 then 方法,并传递不同的回调函数。这种情况下,每个回调函数都会被存储在对应的数组中,并在适当的时候依次执行。

  • 代码

    // 首先定义三种状态
    const PENDING = "pending"
    const FULFILLED = "fulfilled"
    const REJECTED = "rejected"
    
    // 定义MyPromise类
    class MyPromise {
        // 定义constructor 并接受一个executor参数
        constructor(executor){
            // 初始状态
            this.status = PENDING
            // 完成状态下的数据
            this.value = undefined
            // 拒绝状态下的数据
            this.reason = undefined
    
            // 定义两个数组用来存储pending状态下的then方法
            this.onResolveList = []
            this.onRejectList = []
    
            // 定义resolve,reject方法用于接受对应数据和改变状态
            const resolve = value=>{
                this.value = value
                this.status = FULFILLED
                // 执行异步下的then方法中的onResolve回调
                this.onResolveList.forEach(fn=>fn())
            }
            const reject = reason=>{
                this.reason = reason
                this.status = REJECTED
                // 执行异步下的then方法中的onReject回调
                this.onRejectList.forEach(fn=>fn())
            }
    
            // 接下来执行executor 执行异常直接抛出错误
            try {
                executor(resolve, reject)
            } catch (error) {
                reject(error)
            }
    
        }
    
        // 定义then方法用于捕获结果并接受两个参数onResolve, onReject分别对应resolve和reject的数据回调
        then(onResolve, onReject){
            if(this.status === FULFILLED) {
                onResolve(this.value)
            }
            if(this.status === REJECTED) {
                onReject(this.reason)
            }
            // 若状态是pending情况下将回调函数保存到对应的数组中
            if(this.status === PENDING) {
                this.onResolveList.push(()=>onResolve(this.value))
                this.onRejectList.push(()=>onReject(this.reason))
            }
        }
    }
    
  • 测试

    new MyPromise((resolve, reject)=>{
        setTimeout(resolve, 1000, 10)
    }).then(value=>{
        console.log(value) // 10
    })
    new MyPromise((resolve, reject)=>{
        setTimeout(reject, 1000, '失败了')
    }).then(value=>{
        console.log(value)
    }, reason=>{
        console.log(reason) // 失败了
    })
    
  • 问题 至此我们就实现了一个简单的MyPromise类,但是Promise还支持链式调用,所以我们还得继续第三版:链式调用

第三版:链式调用

  • 需要解决的问题

    new Promise((resolve, reject)=>{
        setTimeout(resolve, 1000, 10)
    })
        .then(value=>{
            console.log(value) // 10
            return {a: 20}
        })
        .then()
        .then(value=>{
            console.log(value) // {a: 20}
            return ()=>30
        })
        .then(value=>{
            console.log(value) // f(){return 30}
            return new Promise((resolve, reject)=>{
                setTimeout(resolve, 1000, 40)
            })
        })
        .then(value=>{
            console.log(value) // 40
            return new Promise((resolve, reject)=>{
                setTimeout(
                    resolve, 
                    1000, 
                    new Promise((resolve, reject)=>{
                        setTimeout(resolve, 1000, 50)
                    })
                )
            })
        })
        .then(value=>{
            console.log(value) // 50
        })
    

    观察上面Promise调用的示例我们可以发现下面几点

    • then可以多次调用,说明then方法每次执行返回的是一个promise对象,而且不是上一个then返回的promise。因为执行自己的then没有任何意义
    • then方法的回调函数可以省略,那么下次调用then方法时返回的是上一次then的返回值
    • then方法可以返回函数和对象
    • then方法返回是promise对象时,下次执行then方法时的回调数据是返回的promise对象执行then方法的回调数据而不是promise对象本身
    • then方法返回的promise如果有嵌套promise,则需要完成递归调用回调返回最终的值

    根据上面的几点我们可以编写下

  • 代码

    // 首先定义三种状态
    const PENDING = "pending"
    const FULFILLED = "fulfilled"
    const REJECTED = "rejected"
    
    // 定义MyPromise类
    class MyPromise {
        // 定义constructor 并接受一个executor参数
        constructor(executor){
            // 初始状态
            this.status = PENDING
            // 完成状态下的数据
            this.value = undefined
            // 拒绝状态下的数据
            this.reason = undefined
    
            // 定义两个数组用来存储pending状态下的then方法
            this.onResolveList = []
            this.onRejectList = []
    
            // 定义resolve,reject方法用于接受对应数据和改变状态
            const resolve = value=>{
                this.value = value
                this.status = FULFILLED
                // 执行异步下的then方法中的onResolve回调
                this.onResolveList.forEach(fn=>fn())
            }
            const reject = reason=>{
                this.reason = reason
                this.status = REJECTED
                // 执行异步下的then方法中的onReject回调
                this.onRejectList.forEach(fn=>fn())
            }
    
            // 接下来执行executor 执行异常直接抛出错误
            try {
                executor(resolve, reject)
            } catch (error) {
                reject(error)
            }
    
        }
    
        // 定义then方法用于捕获结果并接受两个参数onResolve, onReject分别对应resolve和reject的数据回调
        then(onResolve, onReject){
    
            // 兼容无参 then().then(...){...}
            onResolve = typeof onResolve === 'function' ? onResolve : v => v
            onReject = typeof onReject === 'function' ? onReject : reason => { throw reason }
    
            const promise2 = new MyPromise((resolve, reject)=>{
                // 处理完成状态下方法
                const handleResolve = ()=>{
                    // then函数中onResolve回调参数的返回值
                    const x = onResolve(this.value) 
                    // 针对返回值的不同类型做处理
                    this.resolveCallbackReturn(promise2, x, resolve, reject)
                }
                // 处理失败下的方法
                const handleReject = ()=>{
                    // then函数中onReject回调参数的返回值
                    const x = onReject(this.reason)
                    this.resolveCallbackReturn(promise2, x, resolve, reject)
                }
    
                if(this.status === FULFILLED) handleResolve()
                if(this.status === REJECTED) handleReject()
                if(this.status === PENDING) {
                    this.onResolveList.push(handleResolve)
                    this.onRejectList.push(handleReject)
                }
            })
    
            return promise2
    
        }
    
        resolveCallbackReturn(promise2, x, reslove, reject){
            // 如果新返回的 Promise 与上一个 then 返回的 Promise 相同,则抛出错误
            if(promise2 === x) {
                return reject("不能返回自身")
            }
    
            // 返回若是promise对象 则调用then方法等待处理完成将结果回调
            if(x instanceof MyPromise) {
                x.then(value=>{
                    // 针对多层Promise 递归获取最终值
                    this.resolveCallbackReturn(promise2, value, reslove, reject)
                }, reason=>{
                    // 针对多层Promise 递归获取最终值
                    this.resolveCallbackReturn(promise2, reason, reslove, reject)
                })
            }else{
                reslove(x)
            }
        }
    }
    
  • 测试

    new MyPromise((resolve, reject)=>{
        setTimeout(resolve, 1000, 10)
    })
        .then(value=>{
            console.log(value) // 10
            return {a: 20}
        })
        .then()
        .then(value=>{
            console.log(value) // {a: 20}
            return ()=>30
        })
        .then(value=>{
            console.log(value) // f(){return 30}
            return new MyPromise((resolve, reject)=>{
                setTimeout(resolve, 1000, 40)
            })
        })
        .then(value=>{
            console.log(value) // 40
            return new MyPromise((resolve, reject)=>{
                setTimeout(
                    resolve, 
                    1000, 
                    new MyPromise((resolve, reject)=>{
                        setTimeout(resolve, 1000, 50)
                    })
                )
            })
        })
        .then(value=>{
            console.log(value) // 50
        })
    

    至此看起来我们能想到的都已经实现了,那么现在结合Promise/A+规范看下我们自己实现的MyPromise是否符合规范

跟着Promise/A+规范走

地址

Promise/A+规范地址:promisesaplus.com/

适配规范

翻看了规范地址后可以了解到

  • 2.1 Promise状态不能改变

    • 示例

      const promise = new MyPromise((resolve, reject) => {
          resolve(1);
          reject(new Error('Rejected')); // 这里状态会逆转为 rejected
      });
      
    • 解决

      // 定义resolve,reject方法用于接受对应数据和改变状态
      const resolve = value=>{
        // PENDING 只能转成FULFILLED或REJECTED
        if(this.status !== PENDING) return
        this.value = value
        this.status = FULFILLED
        // 执行异步下的then方法中的onResolve回调
        this.onResolveList.forEach(fn=>fn())
      }
      const reject = reason=>{
        // PENDING 只能转成FULFILLED或REJECTED
        if(this.status !== PENDING) return
        this.reason = reason
        this.status = REJECTED
        // 执行异步下的then方法中的onReject回调
        this.onRejectList.forEach(fn=>fn())
      }
      
  • 2.3.1: 如果promisex引用相同的对象,promise则以 aTypeError作为理由拒绝

    • 我们的

      // 如果新返回的 Promise 与上一个 then 返回的 Promise 相同,则抛出错误
      if(promise2 === x) {
        return reject("不能返回自身")
      }
      
    • 解决

      if(promise2 === x) {
        return reject(new TypeError("不能返回自身"))
      }
      
  • 3.1: 这里的“平台代码”指的是引擎、环境和 Promise 实现代码。实际上,此要求确保在调用事件循环之后异步执行onFulfilled,并使用新的堆栈。这可以通过“宏任务”机制(例如或 )或“微任务”机制来实现。

    • 示例

      new MyPromise((resolve, reject)=>{
          resolve(1)
      }).then(value=>{
          console.log(value)
      })
      console.log(2)
      /**
       * 输出结果 
       * 1
       * 2
       */
      
    • 解决

      // 处理完成状态下方法
      const handleResolve = ()=>{
        // 模拟异步任务
        queueMicrotask(()=>{
          // then函数中onResolve回调参数的返回值
          const x = onResolve(this.value) 
          // 针对返回值的不同类型做处理
          this.resolveCallbackReturn(promise2, x, resolve, reject)
        })
      }
      // 处理失败下的方法
      const handleReject = ()=>{
        queueMicrotask(()=>{
          // then函数中onReject回调参数的返回值
          const x = onReject(this.reason)
          this.resolveCallbackReturn(promise2, x, resolve, reject)
        })
      }
      
  • 2.3: 这种对 thenable 的处理允许 Promise 实现进行互操作,只要它们公开 Promises/A+ 兼容的then方法。它还允许 Promises/A+ 实现用合理的then方法“同化”不合格的实现。

  • 2.3.3.2: 如果检索属性x.then导致抛出异常e,请拒绝promise作为e原因

  • 2.3.3.3 如果同时resolvePromise调用 和rejectPromise,或者对同一参数进行多次调用,则第一个调用优先,并且忽略任何后续调用。

    • 示例

      // 若then返回的对象包含then的实现规范,同样需要实现then的最终回调
      new Promise((resolve, reject)=>{
        resolve(1)
      }).then(value=>{
        return {
          a: 1,
          then(resolve, reject){
            setTimeout(resolve, 1000, this.a)
            // 会忽略
            setTimeout(resolve, 1000, this.a + 1)
      		}
        }
      }).then(value=>{
        console.log(value) // 1
      })
      
    • 实现

      resolveCallbackReturn(promise2, x, resolve, reject){
              // 如果新返回的 Promise 与上一个 then 返回的 Promise 相同,则抛出错误
              if(promise2 === x) {
                  return reject(new TypeError("不能返回自身"))
              }
              
              // 返回若是promise对象 则调用then方法等待处理完成将结果回调
              if(x && (typeof x === 'object' || typeof x === 'function')){
              		// 当调用 then 方法返回的对象或函数中的 then 方法时,我们需要确保只调用一次 resolvePromise 或 rejectPromise。这是因为规范要求 then 方法只能调用一次,如果多次调用将被忽略。    
                	let called = false
                  // 兼容thenable
                  try {
                      const then = x.then
                      if(typeof then === 'function') {
                          then.call(x, value=>{
                              if (called) return
                              called = true
                              this.resolveCallbackReturn(promise2, value, resolve, reject)
                          }, reason=>{
                              if (called) return
                              called = true
                              // this.resolveCallbackReturn(promise2, reason, resolve, reject)
                              reject(reason)
                          })
                      }else{
                          if (called) return
                          called = true
                          resolve(x)
                      } 
                  }catch (error) {
                      if (called) return
                      called = true
                      reject(error)
                  }
              }else{
                  resolve(x)
              }
          }
      

代码测试

安装

yarn add promises-aplus-tests -D

测试配置

MyPromise.deferred = function () {
  var result = {}
  result.promise = new MyPromise(function (resolve, reject) {
    result.resolve = resolve
    result.reject = reject
  })

  return result
}
module.exports = MyPromise

命令

npx promises-aplus-tests path

最终代码

// 首先定义三种状态
const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"

// 定义MyPromise类
class MyPromise {
    // 定义constructor 并接受一个executor参数
    constructor(executor){
        // 初始状态
        this.status = PENDING
        // 完成状态下的数据
        this.value = undefined
        // 拒绝状态下的数据
        this.reason = undefined

        // 定义两个数组用来存储pending状态下的then方法
        this.onResolveList = []
        this.onRejectList = []

        // 定义resolve,reject方法用于接受对应数据和改变状态
        const resolve = value=>{
            // PENDING 只能转成FULFILLED或REJECTED
            if(this.status !== PENDING) return
            this.value = value
            this.status = FULFILLED
            // 执行异步下的then方法中的onResolve回调
            this.onResolveList.forEach(fn=>fn())
        }
        const reject = reason=>{
            // PENDING 只能转成FULFILLED或REJECTED
            if(this.status !== PENDING) return
            this.reason = reason
            this.status = REJECTED
            // 执行异步下的then方法中的onReject回调
            this.onRejectList.forEach(fn=>fn())
        }

        // 接下来执行executor 执行异常直接抛出错误
        try {
            executor(resolve, reject)
        } catch (error) {
            reject(error)
        }

    }

    // 定义then方法用于捕获结果并接受两个参数onResolve, onReject分别对应resolve和reject的数据回调
    then(onResolve, onReject){

        // 兼容无参 then().then(...){...}
        onResolve = typeof onResolve === 'function' ? onResolve : v => v
        onReject = typeof onReject === 'function' ? onReject : reason => { throw reason }

        const promise2 = new MyPromise((resolve, reject)=>{
            // 处理完成状态下方法
            const handleResolve = ()=>{
                // 模拟异步任务
                queueMicrotask(()=>{
                    try {
                        // then函数中onResolve回调参数的返回值
                        const x = onResolve(this.value) 
                        // 针对返回值的不同类型做处理
                        this.resolveCallbackReturn(promise2, x, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                })
            }
            // 处理失败下的方法
            const handleReject = ()=>{
                queueMicrotask(()=>{
                    try {
                        // then函数中onReject回调参数的返回值
                        const x = onReject(this.reason)
                        this.resolveCallbackReturn(promise2, x, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                })
            }

            if(this.status === FULFILLED) handleResolve()
            if(this.status === REJECTED) handleReject()
            if(this.status === PENDING) {
                this.onResolveList.push(handleResolve)
                this.onRejectList.push(handleReject)
            }
        })

        return promise2

    }

    resolveCallbackReturn(promise2, x, resolve, reject){
        // 如果新返回的 Promise 与上一个 then 返回的 Promise 相同,则抛出错误
        if(promise2 === x) {
            return reject(new TypeError("不能返回自身"))
        }
        
        // 返回若是promise对象 则调用then方法等待处理完成将结果回调
        if(x && (typeof x === 'object' || typeof x === 'function')){
            // 当调用 then 方法返回的对象或函数中的 then 方法时,我们需要确保只调用一次 resolvePromise 或 rejectPromise。这是因为规范要求 then 方法只能调用一次,如果多次调用将被忽略。
            let called = false
            // 兼容thenable
            try {
                const then = x.then
                if(typeof then === 'function') {
                    then.call(x, value=>{
                        if (called) return
                        called = true
                        this.resolveCallbackReturn(promise2, value, resolve, reject)
                    }, reason=>{
                        if (called) return
                        called = true
                        // this.resolveCallbackReturn(promise2, reason, resolve, reject)
                        reject(reason)
                    })
                }else{
                    if (called) return
                    called = true
                    resolve(x)
                } 
            }catch (error) {
                if (called) return
                called = true
                reject(error)
            }
        }else{
            resolve(x)
        }
    }
}