手写实现Promise

Mr.Hotsuitor小于 1 分钟javascriptPromise面试

手写实现Promise

/*
 * File Created: Tuesday, 14th March 2023 3:39:42 pm
 * Author: hotsuitor (hotsuitor@qq.com)
 * -----
 * Last Modified: Tuesday, 14th March 2023 4:35:43 pm
 * Modified By: hotsuitor (hotsuitor@qq.com>)
 * -----
 */

const PROMISE_STATUS_PENDING = 'pending' // 等待状态
const PROMISE_STATUS_FULFILED = 'fulfilled' // 成功状态
const PROMISE_STATUS_REJECTED = 'rejected' // 失败状态

// help function
function execFunctionWithCatchError(execFn, value, resolve, reject) {
  try {
    const result = execFn(value)
    resolve(result)
  } catch (error) {
    reject(error)
  }
}
class MyPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING // promise状态
    this.value = undefined // resolve返回值
    this.reason = undefined // reject返回值
    this.onFulFilledFns = [] // 保存成功回调
    this.onRejectedFns = [] // 保存失败回调

    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return
          this.status = PROMISE_STATUS_FULFILED
          this.value = value
          this.onFulFilledFns.forEach((fn) => {
            fn(this.value)
          })
        })
      }
    }

    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return
          this.status = PROMISE_STATUS_REJECTED
          this.reason = reason
          this.onRejectedFns.forEach((fn) => {
            fn(this.reason)
          })
        })
      }
    }

    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = onFulfilled || ((value) => value)
    onRejected =
      onRejected ||
      ((err) => {
        throw err
      })

    return new MyPromise((resolve, reject) => {
      // 1. when operate then,  status has confirmed
      if (this.status === PROMISE_STATUS_FULFILED && onFulfilled) {
        execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
      }
      if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
        execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
      }

      if (this.status === PROMISE_STATUS_PENDING) {
        if (onFulfilled) {
          this.onFulFilledFns.push(() => {
            execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
          })
        }
        if (onRejected) {
          this.onRejectedFns.push(() => {
            execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
          })
        }
      }
    })
  }

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

  finally(onFinally) {
    this.then(
      () => {
        onFinally()
      },
      () => {
        onFinally()
      }
    )
  }

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

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

  static all(promises) {
    return new MyPromise((resolve, reject) => {
      const values = []
      promises.forEach((promise) => {
        promise.then(
          (res) => {
            values.push(res)
            if (values.length === promises.length) {
              resolve(values)
            }
          },
          (err) => reject(err)
        )
      })
    })
  }

  static allSettled(promises) {
    return new MyPromise((resolve) => {
      const results = []
      promises.forEach((promise) => {
        promise.then(
          (res) => {
            results.push({ status: PROMISE_STATUS_FULFILED, value: res })
            if (results.length === promises.length) {
              resolve(results)
            }
          },
          (err) => {
            results.push({ status: PROMISE_STATUS_REJECTED, value: err })
            if (results.length === results.length) {
              resolve(results)
            }
          }
        )
      })
    })
  }
  static race(promises) {
    return new MyPromise((resolve, reject) => {
      promises.forEach((promise) => {
        promise.then(resolve, reject)
      })
    })
  }

  static any(promises) {
    return new MyPromise((resolve, reject) => {
      const reasons = []
      promises.forEach((promise) => {
        promise.then(resolve, (err) => {
          reasons.push(err)
          if (reasons.length === promises.length) {
            reject(reasons)
          }
        })
      })
    })
  }
}

/** test */
const p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    console.log('--- 1 ---')
    resolve(111)
  })
}).then((res) => {
  console.log('p1 res :>> ', res)
})

const p2 = new MyPromise((resolve, reject) => {
  console.log('--- 2 ---')
  resolve(222)
})

const p3 = new MyPromise((resolve, reject) => {
  console.log('--- 3 ---')
  resolve(333)
})

const p4 = new MyPromise((resolve, reject) => {
  console.log('--- 4 ---')
  reject(444)
})

MyPromise.all([p2, p3]).then((res) => {
  console.log('p2&p3 res :>> ', res)
})

MyPromise.all([p2, p4])
  .then((res) => {
    console.log('p2&p4 res :>> ', res)
  })
  .catch((err) => {
    console.log('err :>> ', err)
  })

应用

限制最大并发请求数

实现一:

/*
 * File Created: Thursday, 18th May 2023 9:13:01 pm
 * Author: hotsuitor (hotsuitor@qq.com)
 * -----
 * Last Modified: Thursday, 18th May 2023 9:14:14 pm
 * Modified By: hotsuitor (hotsuitor@qq.com>)
 */

class RequestLimit {
  constructor(limit = 5) {
    this.count = 0
    this.limit = limit
    this.queue = [] // 数组模拟请求队列
  }

  async add(fn) {
    if (typeof fn !== 'function') return
    if (this.count >= this.limit) {
      await new Promise((resolve) => this.queue.push(resolve))
    }
    this.count++
    try {
      return await fn()
    } finally {
      this.count--
      if (this.queue.length > 0) {
        this.queue.shift()()
      }
    }
  }
}

// ===test===

const sleep = async (time) => {
  await new Promise((resolve) => setTimeout(resolve, time * 1000))
}
const limit = new RequestLimit()
for (let i = 1; i < 21; i++) {
  limit.add(() =>
    fetch(`https://jsonplaceholder.typicode.com/todos/${i}`)
      .then((res) => {
        return res.json()
      })
      .then((res) => {
        console.log(i, res)
      })
  )
}

实现二:

/*
 * File Created: Thursday, 18th May 2023 10:37:33 pm
 * Author: hotsuitor (hotsuitor@qq.com)
 * -----
 * Last Modified: Thursday, 18th May 2023 10:37:38 pm
 * Modified By: hotsuitor (hotsuitor@qq.com>)
 */
class RequestLimit {
  constructor(limit = 5) {
    this.limit = limit
    this.count = 0
    this.queue = [] // 并发队列
  }

  async add(fn) {
    if (this.count < this.limit) {
      this.count++
      return fn().finally(() => {
        this.count--
        this.next()
      })
    } else {
      return new Promise((resolve) => {
        this.queue.push(() => {
          this.count++
          resolve(
            fn().finally(() => {
              this.count--
              this.next()
            })
          )
        })
      })
    }
  }

  next() {
    if (this.count < this.limit && this.queue.length > 0) {
      this.queue.shift()()
    }
  }
}

// ===test===

const sleep = async (time) => {
  await new Promise((resolve) => setTimeout(resolve, time * 1000))
}
const limit = new RequestLimit()
for (let i = 1; i < 21; i++) {
  limit.add(() =>
    fetch(`https://jsonplaceholder.typicode.com/todos/${i}`)
      .then((res) => {
        return res.json()
      })
      .then((res) => {
        console.log(i, res)
      })
  )
}