JS Advance --- promise的简单实现

444 阅读9分钟

基本结构的实现

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

class CustomPromise {
  #STATUS_PENDING = 'pending'
  #STATUS_FULFILLED = 'fulfilled'
  #STATUS_REJECTED = 'rejected'

  #status = this.#STATUS_PENDING

  #resolve = () => {}
  #reject = () => {}

  constructor(executor) {
    const resolve = value => {
      if (this.#status === this.#STATUS_PENDING) {
        this.#status = this.#STATUS_FULFILLED

        // 执行到此处的时候,只是在执行executor方法
        // 并没有真正执行到then方法
        // 所以使用queueMicrotask将resolve或reject函数的执行放入到当前事件队列的微任务队列中
        // 让其不阻塞主线程的执行,等到主线程全部执行完毕以后,浏览器会自动去执行当前事件事件队列中的微任务队列
        queueMicrotask(() => {
          this.#resolve(value)
        })
      }
    }

    const reject = reason => {
      if (this.#status === this.#STATUS_PENDING) {
        this.#status = this.#STATUS_REJECTED

        queueMicrotask(() => {
          this.#reject(reason)
        })
      }
    }

    executor(resolve, reject)
  }

  then(onresolved, onrejected) {
    this.#resolve = onresolved
    this.#reject = onrejected
  }
}

// 测试代码
const promise = new CustomPromise((resolve, reject) => {
  resolve(123)
  reject('error')
})

promise.then(res => console.log(res), err => console.log(err))

实例方法

then方法

多次执行then方法

之前在promise基本结构实现的代码中,我们如果多次调用then方法,后一个then方法会将之前的then方法覆盖

// 因为内部实现是  this.#resolve = onresolved
// 所以多次进行promise.then调用的时候,后一个会将前一个给覆盖掉
promise.then(res => console.log(res), err => console.log(err))
promise.then(res => console.log('res:' + res), err => console.log('err' + err))

所以实际上,我们应该是要数组存储所有传入的onresolve和onreject方法

class CustomPromise {
  #STATUS_PENDING = 'pending'
  #STATUS_FULFILLED = 'fulfilled'
  #STATUS_REJECTED = 'rejected'

  #status = this.#STATUS_PENDING

  #resolveFns = []
  #rejecetFns = []

  constructor(executor) {
    const resolve = value => {
      if (this.#status === this.#STATUS_PENDING) {
        this.#status = this.#STATUS_FULFILLED

        queueMicrotask(() => {
          this.#resolveFns.forEach(fn => fn(value))
        })
      }
    }

    const reject = reason => {
      if (this.#status === this.#STATUS_PENDING) {
        this.#status = this.#STATUS_REJECTED

        queueMicrotask(() => {
          this.#rejecetFns.forEach(fn => fn(reason))
        })
      }
    }

    executor(resolve, reject)
  }

  then(onresolved, onrejected) {
    if (onresolved) {
      this.#resolveFns.push(onresolved)
    }

    if (onrejected) {
      this.#rejecetFns.push(onrejected)
    }
  }
}

// 测试代码
const promise = new CustomPromise((resolve, reject) => {
  resolve(123)
  reject('error')
})

promise.then(res => console.log(res), err => console.log(err))
promise.then(res => console.log('res: ' + res), err => console.log('err: ' + err))

但是此时依旧存在一个问题

promise.then(res => console.log(res), err => console.log(err))
promise.then(res => console.log('res: ' + res), err => console.log('err: ' + err))

setTimeout(() => {
  // 这里的then方法并不会被正常回调
  // 因为此时executor中的resolve方法依旧被执行完毕了
  // 此时仅仅只是向数组中添加需要执行的函数体,但是并不会实际执行
  promise.then(res => console.log('setTimeout res: ' + res), err => console.log('setTimeout err: ' + err))
}, 1000)

所以此时需要对代码进行相应的改进

class CustomPromise {
  #STATUS_PENDING = 'pending'
  #STATUS_FULFILLED = 'fulfilled'
  #STATUS_REJECTED = 'rejected'

  #status = this.#STATUS_PENDING

  #resolveFns = []
  #rejecetFns = []

  #value = undefined
  #reason = undefined

  constructor(executor) {

    const resolve = value => {
      // add microtask
      queueMicrotask(() => {
        if (this.#status === this.#STATUS_PENDING) {
          this.#status = this.#STATUS_FULFILLED
          this.#value = value
          this.#resolveFns.forEach(fn => fn(value))
        }
      })
    }

    const reject = reason => {
      // add microtask
      queueMicrotask(() => {
        if (this.#status === this.#STATUS_PENDING) {
          this.#status = this.#STATUS_REJECTED
          this.#reason = reason
          this.#rejecetFns.forEach(fn => fn(reason))
        }
      })
    }

    executor(resolve, reject)
  }

  then(onresolved, onrejected) {
    // 如果当前状态已经是resolved或rejected, 表明状态已经确定,直接执行对应的函数即可
    if (onresolved && this.#status === this.#STATUS_FULFILLED) {
      onresolved(this.#value)
    }

    if (onrejected && this.#status === this.#STATUS_REJECTED) {
      onrejected(this.#reason)
    }

    // 如果当前状态是pending,那么所有的resolved或rejected函数需要存放于数组中,于后期进行调用
    if (this.#status === this.#STATUS_PENDING) {
      if (onresolved) {
        this.#resolveFns.push(onresolved)
      }

      if (onrejected) {
        this.#rejecetFns.push(onrejected)
      }
   }
  }
}

// 测试代码
const promise = new CustomPromise((resolve, reject) => {
  resolve(123)
  reject('error')
})

promise.then(res => console.log(res), err => console.log(err))
promise.then(res => console.log('res: ' + res), err => console.log('err: ' + err))

setTimeout(() => {
  promise.then(res => console.log('setTimeout res: ' + res), err => console.log('setTimeout err: ' + err))
}, 1000)

链式调用

promise.then方法返回值应该是一个新的promise,但是之前的代码逻辑then方法没有返回值,所以是无法实现链式调用的

对此需要进行相应的修改

// tool function ---> 对promise实例的try ... catch ... 方法的封装
function catchPromiseError(fn, param, resolve, reject) {
  try {
    resolve(fn(param))
  } catch (err) {
    reject(err)
  }
}

class CustomPromise {
  #STATUS_PENDING = 'pending'
  #STATUS_FULFILLED = 'fulfilled'
  #STATUS_REJECTED = 'rejected'

  #resolveFns = []
  #rejecetFns = []

  #value = undefined
  #reason = undefined

  constructor(executor) {
    this.status = this.#STATUS_PENDING

    const resolve = value => {
      // add microtask
      queueMicrotask(() => {
        if (this.status === this.#STATUS_PENDING) {
          this.status = this.#STATUS_FULFILLED
          this.#value = value
          this.#resolveFns.forEach(fn => fn(value))
        }
      })
    }

    const reject = reason => {
      // add microtask
      queueMicrotask(() => {
        if (this.status === this.#STATUS_PENDING) {
          this.status = this.#STATUS_REJECTED
          this.#reason = reason
          this.#rejecetFns.forEach(fn => fn(reason))
        }
      })
    }

    executor(resolve, reject)
  }

  then(onresolved, onrejected) {
    // 返回值需要一个promise, 而promise的executor函数会立即执行
    // 所以直接使用一个新的promise包裹then方法中的逻辑实现
    return new CustomPromise((resolve, reject) => {
      if (onresolved && this.status === this.#STATUS_FULFILLED) {
        catchPromiseError(onresolved, this.#value, resolve, reject)
      }

      if (onrejected && this.status === this.#STATUS_REJECTED) {
        catchPromiseError(onrejected, this.#reason, resolve, reject)
      }

      if (this.status === this.#STATUS_PENDING) {
        if (onresolved) {
          // 使用闭包 
          // 传入的是外层的函数,但实际执行的是内部的catchPromiseError方法
          // 目的是为了获取正确的resolve和reject方法的值
          this.#resolveFns.push(value => catchPromiseError(onresolved, value, resolve, reject))
        }

        if (onrejected) {
          this.#rejecetFns.push(reason => catchPromiseError(onrejected, reason, resolve, reject))
        }
      }
    })
  }
}

// 测试代码
const promise = new CustomPromise((resolve, reject) => {
  // resolve(123)
  reject('error')
})

promise
  .then(res => {
    console.log('res1: ' + res)
    // return 'res1 resolved return value'
    throw new Error('error in res1 resolved')
  }, err => {
    console.log('err1: ' + err)
    return 'errr1 rejected return value'
  })
  .then(res => console.log('res2: ' + res), err => console.log('err2: ' + err))

setTimeout(() => {
  promise.then(res => console.log('setTimeout res: ' + res), err => console.log('setTimeout err: ' + err))
}, 1000)

但是此时可能返回值是一个新的promise实例,也可能是一个实现了thenable的对象

function catchPromiseError(fn, param, resolve, reject, status) {
  try {
    const res = fn(param)

    // 如果外层promise的状态为resolved的时候
    if (status === 'resolved') {
      // 返回值是一个新的promise
      if (res instanceof CustomPromise) {
        // 执行返回的promise, 以确定实际返回值的状态
        res.then(res => resolve(res), err => reject(err))
      } else if (typeof res?.then === 'function') {
        // 如果返回值是一个实现了then方法的对象, 最终状态由thenable对象决定
        res.then(resolve, reject)
      } else {
        resolve(res)
      }
    } else {
      resolve(res)
    }
  } catch (err) {
    reject(err)
  }
}

class CustomPromise {
  #STATUS_PENDING = 'pending'
  #STATUS_FULFILLED = 'fulfilled'
  #STATUS_REJECTED = 'rejected'

  #status = this.#STATUS_PENDING

  #resolveFns = []
  #rejecetFns = []

  #value = undefined
  #reason = undefined

  constructor(executor) {
    const resolve = value => {
      // add microtask
      queueMicrotask(() => {
        if (this.#status === this.#STATUS_PENDING) {
          this.#status = this.#STATUS_FULFILLED
          this.#value = value
          this.#resolveFns.forEach(fn => fn(value))
        }
      })
    }

    const reject = reason => {
      // add microtask
      queueMicrotask(() => {
        if (this.#status === this.#STATUS_PENDING) {
          this.#status = this.#STATUS_REJECTED
          this.#reason = reason
          this.#rejecetFns.forEach(fn => fn(reason))
        }
      })
    }

    executor(resolve, reject)
  }

  then(onresolved, onrejected) {
    return new CustomPromise((resolve, reject) => {
      if (onresolved && this.#status === this.#STATUS_FULFILLED) {
        catchPromiseError(onresolved, this.#value, resolve, reject, 'resolved')
      }

      if (onrejected && this.#status === this.#STATUS_REJECTED) {
        catchPromiseError(onrejected, this.#reason, resolve, reject)
      }

      if (this.#status === this.#STATUS_PENDING) {
        if (onresolved) {
          this.#resolveFns.push(value => catchPromiseError(onresolved, value, resolve, reject, 'resolved'))
        }

        if (onrejected) {
          this.#rejecetFns.push(reason => catchPromiseError(onrejected, reason, resolve, reject))
        }
      }
    })
  }
}

// 测试代码
const promise = new CustomPromise((resolve, reject) => {
  resolve(123)
  // reject('error')
})

promise
  .then(res => {
    console.log('res1: ' + res)
    // return new CustomPromise((resolve, reject) => reject('something error in return promise'))
    return {
      then(resolve, reject) {
        reject('rejected in thenable object')
      }
    }
  }, err => {
    console.log('err1: ' + err)
  })
  .then(res => console.log('res2: ' + res), err => console.log('err2: ' + err))

catch方法

function catchPromiseError(fn, param, resolve, reject, status) {
  try {
    const res = fn(param)

    if (status === 'resolved') {
      if (res instanceof CustomPromise) {
        res.then(res => resolve(res), err => reject(err))
      } else if (typeof res?.then === 'function') {
        res.then(resolve, reject)
      } else {
        resolve(res)
      }
    } else {
       resolve(res)
    }
  } catch (err) {
    reject(err)
  }
}

class CustomPromise {
  #STATUS_PENDING = 'pending'
  #STATUS_FULFILLED = 'fulfilled'
  #STATUS_REJECTED = 'rejected'

  #status = this.#STATUS_PENDING

  #resolveFns = []
  #rejecetFns = []

  #value = undefined
  #reason = undefined

  constructor(executor) {
    const resolve = value => {
      queueMicrotask(() => {
        if (this.#status === this.#STATUS_PENDING) {
          this.#status = this.#STATUS_FULFILLED
          this.#value = value
          this.#resolveFns.forEach(fn => fn(value))
        }
      })
    }

    const reject = reason => {
      queueMicrotask(() => {
        if (this.#status === this.#STATUS_PENDING) {
          this.#status = this.#STATUS_REJECTED
          this.#reason = reason
          this.#rejecetFns.forEach(fn => fn(reason))
        }
      })
    }

    executor(resolve, reject)
  }

  // 如果没有传入onrejected方法,那么就使用默认值
  // 如果then自身没有处理异常,就交给下一个then方法或catch方法进行处理
  then(onresolved, onrejected = err => { throw err }) {
    return new CustomPromise((resolve, reject) => {
      if (onresolved && this.#status === this.#STATUS_FULFILLED) {
        catchPromiseError(onresolved, this.#value, resolve, reject, 'resolved')
      }

      if (onrejected && this.#status === this.#STATUS_REJECTED) {
        catchPromiseError(onrejected, this.#reason, resolve, reject)
      }

      if (this.#status === this.#STATUS_PENDING) {
        if (onresolved) {
          this.#resolveFns.push(value => catchPromiseError(onresolved, value, resolve, reject, 'resolved'))
        }

        if (onrejected) {
          this.#rejecetFns.push(reason => catchPromiseError(onrejected, reason, resolve, reject))
        }
      }
    })
  }

  catch(onrejected) {
    // catch本质上就是一个特殊的 resolved函数的值为undefined的 then方法
    // catch方法依旧需要返回一个新的promise实例
    return this.then(undefined, onrejected)
  }
}

// 测试代码
const promise = new CustomPromise((resolve, reject) => {
  reject('error')
})

promise
  .then(res => {
    console.log('res1: ' + res)
  })
  // 这里的catch本质上接收的应该是promise.then方法返回的promise的rejected方法
  // 而不是promise返回的rejected方法
  .catch(err => console.log('err2: ' + err))

// 当promise自身可以处理状态为rejected的时候, 就不需要catch方法来处理对应逻辑了
promise
  .then(res => {
    console.log('res1: ' + res)
  }, err => console.log('err1: ' + err))
  .catch(err => console.log('err2: ' + err))

finally方法

function catchPromiseError(fn, param, resolve, reject, status) {
  try {
    const res = fn(param)

    if (status === 'resolved') {
      if (res instanceof CustomPromise) {
        res.then(res => resolve(res), err => reject(err))
      } else if (typeof res?.then === 'function') {
        res.then(resolve, reject)
      } else {
        resolve(res)
      }
    } else {
       resolve(res)
    }
  } catch (err) {
    reject(err)
  }
}

class CustomPromise {
  #STATUS_PENDING = 'pending'
  #STATUS_FULFILLED = 'fulfilled'
  #STATUS_REJECTED = 'rejected'

  #status = this.#STATUS_PENDING

  #resolveFns = []
  #rejecetFns = []

  #value = undefined
  #reason = undefined

  constructor(executor) {
    const resolve = value => {
      queueMicrotask(() => {
        if (this.#status === this.#STATUS_PENDING) {
          this.#status = this.#STATUS_FULFILLED
          this.#value = value
          this.#resolveFns.forEach(fn => fn(value))
        }
      })
    }

    const reject = reason => {
      queueMicrotask(() => {
        if (this.#status === this.#STATUS_PENDING) {
          this.#status = this.#STATUS_REJECTED
          this.#reason = reason
          this.#rejecetFns.forEach(fn => fn(reason))
        }
      })
    }

    executor(resolve, reject)
  }

  // 因为catch之后依旧可以使用then方法,而catch是不会处理resolve状态的
  // 所以需要将上一个promise返回的resolve状态的值进行转发
  // 交给下一个then方法来进行处理
  then(onresolved = value => value, onrejected = err => { throw err }) {
    return new CustomPromise((resolve, reject) => {
      if (this.#status === this.#STATUS_FULFILLED) {
        catchPromiseError(onresolved, this.#value, resolve, reject, 'resolved')
      }

      if (this.#status === this.#STATUS_REJECTED) {
        catchPromiseError(onrejected, this.#reason, resolve, reject)
      }

      if (this.#status === this.#STATUS_PENDING) {
        this.#resolveFns.push(value => catchPromiseError(onresolved, value, resolve, reject, 'resolved'))

        this.#rejecetFns.push(reason => catchPromiseError(onrejected, reason, resolve, reject))
      }
    })
  }

  catch(onrejected) {
    return this.then(undefined, onrejected)
  }

  finally(onfinally) {
    // finally不需要在有任何的返回值
    // finally也是一个特殊的then方法 --- resolve方法和rejeced方法的实现逻辑是一致的
    this.then(onfinally, onfinally)
  }
}

// 测试代码
const promise = new CustomPromise((resolve, reject) => {
  resolve(123)
  // reject('error')
})

promise
  .catch(err => {
    console.log('err2: ' + err)
    return 123
  })
  .then(res => console.log('res: ' + res))
  .finally(() => console.log('finally'))

静态方法

resolve 和 reject

{
  // ... 此处进行代码省略
  
  static resolve(value) {
    return new CustomPromise(resolve => resolve(value))
  }

  static reject(reason) {
    return new CustomPromise((resolve, reject) => reject(reason))
  }
  
  // ... 此处进行代码省略
}

all 和 allSettled

all

static all(promises) {
  let count = 0
  const results = []

  return new CustomPromise((resolve, reject) => {
    // 使用IIFE,是为了保证返回的resolve中的数组的顺序和传入的顺序保持一致
    // 而不是和执行的先后顺序保持一致
    promises.forEach((promise, index) => {
      (function(index) {
        // 如果传入的不是Promise实例,需要转换为promise实例对象
        if (!(promise instanceof CustomPromise)) {
          promise = CustomPromise.resolve(promise)
        }

        promise.then(res => {
          results[index] = res
          count++

          if (count === promises.length) {
            resolve(results)
          }
        }, reject) // 这里的reject等价于err => reject(err)
      })(index)
    })
  })
}

allSettled

// allSettled方法会等到所有的promise都有结果后,才会返回结果
// allSettled方法 只会执行resolved方法,永远不会执行rejected方法
static allSettled(promises) {
  const results = []
  let count = 0

  return new CustomPromise(resolve => {
    promises.forEach((promise, index) => {
      if (!(promise instanceof CustomPromise)) {
         promise = CustomPromise.resolve(promise)
      }
      
      (function(index) {
        promise.then(res => {
          results[index] = {
            status: 'fulfilled',
            value: res
          }
          count++

          if(count === promises.length){
            resolve(results)
          }
        }, err => {
          results[index] = {
            stauts: 'rejected',
            reason: err
          }
          count++

          if(count === promises.length){
            resolve(results)
          }
        })
      })(index)
    })
  })
}

race 和 any

race

static race(promises) {
  return new CustomPromise((resolve, reject) => {
    promises.forEach(promise => {
      if (!(promise instanceof CustomPromise)) {
        promise = CustomPromise.resolve(promise)
      }

      promise.then(resolve, reject)
    })
  })
}

any

static any(promises) {
  const results = []
  let count = 0

  return new CustomPromise((resolve, reject) => {
    promises.forEach((promise, index) => {
      if (!(promise instanceof CustomPromise)) {
        promise = CustomPromise.resolve(promise)
      }

      (function(index) {
        promise.then(resolve, err => {
          results[index] = err
          count++

          if (count === promises.length) {
            // 目前node的LTS版本是 v14.18.1
            // AggregateError是ES2021提出的新内容
            // 所以any方法需要在current版本的node中或最新版本的浏览器中进行测试
            reject(new AggregateError(results, 'All promises were rejected'))
          }
        })
      })(index)
    })
  })
}

完整实现

function catchPromiseError(fn, param, resolve, reject, status) {
  try {
    const res = fn(param)

    if (status === 'resolved') {
      if (res instanceof CustomPromise) {
        res.then(res => resolve(res), err => reject(err))
      } else if (typeof res?.then === 'function') {
        res.then(resolve, reject)
      }
    } else {
       resolve(res)
    } else {
      resolve(res)
    }
  } catch (err) {
    reject(err)
  }
}

class CustomPromise {
  #STATUS_PENDING = 'pending'
  #STATUS_FULFILLED = 'fulfilled'
  #STATUS_REJECTED = 'rejected'

  #status = this.#STATUS_PENDING

  #resolveFns = []
  #rejecetFns = []

  #value = undefined
  #reason = undefined

  constructor(executor) {
    const resolve = value => {
      queueMicrotask(() => {
        if (this.#status === this.#STATUS_PENDING) {
          this.#status = this.#STATUS_FULFILLED
          this.#value = value
          this.#resolveFns.forEach(fn => fn(value))
        }
      })
    }

    const reject = reason => {
      queueMicrotask(() => {
        if (this.#status === this.#STATUS_PENDING) {
          this.#status = this.#STATUS_REJECTED
          this.#reason = reason
          this.#rejecetFns.forEach(fn => fn(reason))
        }
      })
    }

    executor(resolve, reject)
  }

  then(onresolved = value => value, onrejected = err => { throw err }) {
    return new CustomPromise((resolve, reject) => {
      if (this.#status === this.#STATUS_FULFILLED) {
        catchPromiseError(onresolved, this.#value, resolve, reject, 'resolved')
      }

      if (this.#status === this.#STATUS_REJECTED) {
        catchPromiseError(onrejected, this.#reason, resolve, reject)
      }

      if (this.#status === this.#STATUS_PENDING) {
        this.#resolveFns.push(value => catchPromiseError(onresolved, value, resolve, reject, 'resolved'))

        this.#rejecetFns.push(reason => catchPromiseError(onrejected, reason, resolve, reject))
      }
    })
  }

  catch(onrejected) {
    return this.then(undefined, onrejected)
  }

  finally(onfinally) {
    this.then(onfinally, onfinally)
  }

  static resolve(value) {
    return new CustomPromise(resolve => resolve(value))
  }

  static reject(reason) {
    return new CustomPromise((resolve, reject) => reject(reason))
  }

  static all(promises) {
    let count = 0
    const results = []

    return new CustomPromise((resolve, reject) => {
      promises.forEach((promise, index) => {
        (function(index) {
          if (!(promise instanceof CustomPromise)) {
            promise = CustomPromise.resolve(promise)
          }

          promise.then(res => {
            results[index] = res
            count++

            if (count === promises.length) {
              resolve(results)
            }
          }, reject)
        })(index)
      })
    })
  }

  static allSettled(promises) {
    const results = []
    let count = 0

      return new CustomPromise(resolve => {
        promises.forEach((promise, index) => {
          if (!(promise instanceof CustomPromise)) {
            promise = CustomPromise.resolve(promise)
          }

         (function(index) {
            promise.then(res => {
              results[index] = {
                status: 'fulfilled',
                value: res
              }
              count++

              if(count === promises.length){
                resolve(results)
              }
            }, err => {
              results[index] = {
                stauts: 'rejected',
                reason: err
              }
              count++

              if(count === promises.length){
                resolve(results)
              }
            })
         })(index)
        })
    })
  }

  static race(promises) {
    return new CustomPromise((resolve, reject) => {
      promises.forEach(promise => {
        if (!(promise instanceof CustomPromise)) {
          promise = CustomPromise.resolve(promise)
        }

        promise.then(resolve, reject)
      })
    })
  }

  static any(promises) {
    const results = []
    let count = 0

    return new CustomPromise((resolve, reject) => {
      promises.forEach((promise, index) => {
        if (!(promise instanceof CustomPromise)) {
          promise = CustomPromise.resolve(promise)
        }

        (function(index) {
          promise.then(resolve, err => {
            results[index] = err
            count++

            if (count === promises.length) {
              reject(new AggregateError(results, 'All promises were rejected'))
            }
         })
        })(index)
      })
    })
  }
}