前端手写题系列之:Promise的那些方法

54 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 5 天,点击查看活动详情

promise的API:race、all、allSettled、any

笔者为了方便自己记忆,把这些方法大概抽离出了一个框架,这样子,写的时候,只需根据API的特性,向其中填入代码。

Promise.base = function (arr) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < arr.length; i++) {
      arr[i].then(resolve, reject)
    }
  })
}

Promise.race

定义:只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

Promise.race = function (arr) {
  console.log('race')
  return new Promise((resolve, reject) => {
    for (let i = 0; i < arr.length; i++) {
      arr[i].then(resolve, reject)
    }
  })
}

Promise.all

定义:p的状态由p1p2p3决定,分成两种情况。

(1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

思路:

  1. all 在 resolve 状态下需要返回成功的数组,因此我们需要定义 result 变量
  2. 当数组遍历结束,则可以返回数组
Promise.all = function (arr) {
  console.log('all')
  return new Promise((resolve, reject) => {
    let result = []
    let count = 0
    for (let i = 0; i < arr.length; i++) {
      arr[i].then(data => {
        result[i] = data
        if (++count === arr.length) {
          resolve(result)
        }
      }, err => {
        reject(err)
      })
    }
  })
}

Promise.allSettled

定义:Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。

思路:

  1. 需要返回一个数组
  2. 对于传入的数据,每一个都去处理相对应的状态
Promise.allSettled = function (arr) {
  console.log('allSettled')
  return new Promise((resolve, reject) => {
    let result = []
    let count = arr.length
    for (let i = 0; i < arr.length; i++) {
      arr[i].then((data) => {
        result[i] = {
          status: 'fufilled',
          value: data
        }
      }, (err) => {
        result[i] = {
          status: 'rejected',
          value: err
        }
      }).finally(() => {
        if (!--count) {
          resolve(result)
        }
      })
    }
  })
}

Promise.any()

Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

思路:

  1. 当数据中有resolve的时候,我们之间 reslove 出去
  2. 全部 reject 的时候,才会调用 reject,因为需要 count 计数器来判断
Promise.any = function (arr) {
  console.log('any')
  return new Promise((resolve, reject) => {
    let count = arr.length
    for (let i = 0; i < arr.length; i++) {
      arr[i].then((data) => {
        resolve(data)
      }, (err) => {
        if (!--count) {
          reject(new AggregateError(err))
        }
      })
    }
  })
}

原生的 ajax 封装成 promise

思路:需要实现的是链式调用,因此函数需要返回 Promise。当请求成功时,调用 resolve,将数据返回。

function ajax(method, url) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest()
    xhr.open(method, url)
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (xhr.status === 200 || xhr.status === 304) {
          resolve(xhr.response)
        }
      }
    }
    xhr.send()
  })
}

ajax('get', 'https://api.github.com/users/suiboyu').then(res => {
  console.log(res)
})

实现一个同时允许任务数量最大为 n 的函数

思路:

  1. 写出基本框架
  2. 什么时候返回,当 finish 等于数组的长度,即完成所有的任务,就可以调用 resolve。
  3. 当有一个任务执行的时候,start 加一。任务执行完成,则 start 减一。
  4. 重复执行 run 函数。
function limitRunTask(tasks, n) {
  return new Promise((resolve, reject) => {
    let finish = 0, result = [], start = 0, index = 0
    function run() {
      if (finish === tasks.length) {
        resolve(result)
        return
      }
      while (start < n && index < tasks.length) {
        start++
        let current = index
        tasks[index++]().then(v => {
          start--
          finish++
          result[current] = v
          run()
        })
      }
    }
    run()
  })
}