javaScripPromise

Promise

# 1. 如何让 Promise.all 在抛出异常后依然有效

  • 如下问题
let p1 = new Promise((resolve, reject) => {
  resolve('p1')
})
let p2 = new Promise((resolve, reject) => {
  reject('p2')
})
let p3 = new Promise((resolve, reject) => {
  reject('p3')
})
// 错误的做法
Promise.all([p1, p2, p3])
  .then(values => {
    console.log(values) // 上面三个Promise有其中任意一个报错后,这里就不返回了
  })
  .catch(err => {
    console.log(err) // p2
  })

// 正确的做法1,返回错误写死
Promise.allSettled([p1, p2, p3].map(p => p.catch(e => '出错后返回的值'))).then(
  values => {
    console.log(values)
  }
)

// 正确的做法2,捕获正常返回错误
Promise.allSettled([p1, p2, p3]).then(values => {
  console.log(values)
})

# 2. async/await 怎么进行错误处理?

//错误示例
const fetchDataA = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('fetch data is A')
    }, 1000)
  })
}

const fetchDataB = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('fetch data is B')
    }, 1000)
  })
}

const fetchDataC = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('fetch data is C')
    }, 1000)
  })
}

try {
  const dataA = await fetchDataA()
  console.log('dataA is ->', dataA)
} catch (err) {
  console.log('err is ->', err)
}

try {
  const dataB = await fetchDataB()
  console.log('dataB is ->', dataB)
} catch (err) {
  console.log('err is ->', err)
}

try {
  const dataC = await fetchDataC()
  console.log('dataC is ->', dataC)
} catch (err) {
  console.log('err is ->', err)
}
const fetchDataA = () => {
  return new Promise((resolve, reject) => {
    reject('reject错误1')
  })
}

const fetchDataB = () => {
  return new Promise((resolve, reject) => {
    resolve('dataB is ->')
  })
}

// 正确做法,抽离成公共方法,去捕获错误
const awaitWrap = promise => {
  return promise.then(data => [null, data]).catch(err => [err, null])
}

const [err, data] = await awaitWrap(fetchDataA())
const [err2, data2] = await awaitWrap(fetchDataB())
console.log('err', err)
console.log('err2', err2)
console.log('---:')
console.log('data', data)
console.log('data2', data2)

function awaitWrap<T, U = any>(
  promise: Promise<T>
): Promise<[U | null, T | null]> {
  return (
    promise.then <
    [null, T] >
    ((data: T) => [null, data]).catch <
    [U, null] >
    (err => [err, null])
  )
}

# 3. 在 map 中使用 await,

map 的返回值始是 promise 数组,这是因为异步函数总是返回 promise

async function test() {
  console.log('start')
  const res = ['js', 'vue', 'node', 'react'].map(async item => {
    return await getSkillPromise(item)
  })
  const resPromise = await Promise.all(res)
  console.log(resPromise)
}

test()

# 4. 控制并发

在处理诸如文件上传等场景时,可能需要限制同时进行的异步操作数量以避免系统资源耗尽

async function asyncPool(poolLimit, array, iteratorFn) {
  const result = []
  const executing = []

  for (const item of array) {
    const p = Promise.resolve().then(() => iteratorFn(item, array))
    result.push(p)

    if (poolLimit <= array.length) {
      const e = p.then(() => executing.splice(executing.indexOf(e), 1))
      executing.push(e)
      if (executing.length >= poolLimit) {
        await Promise.race(executing)
      }
    }
  }
  return Promise.all(result)
}

// 示例
async function uploadFile(file) {
  // 文件上传逻辑
}

async function limitedFileUpload(files) {
  return asyncPool(3, files, uploadFile)
}

# 5. 递归优化

// 异步递归函数
async function asyncRecursiveSearch(nodes) {
  for (const node of nodes) {
    await asyncProcess(node)
    if (node.children) {
      await asyncRecursiveSearch(node.children)
    }
  }
}

// 示例
async function asyncProcess(node) {
  // 对节点进行异步处理逻辑
}

# 6. 在 async 函数中使用 await 链式调用

class ApiClient {
  constructor() {
    this.value = null
  }

  async firstMethod() {
    this.value = await fetch('/first-url').then(r => r.json())
    return this
  }

  async secondMethod() {
    this.value = await fetch('/second-url').then(r => r.json())
    return this
  }
}

// 使用方式
const client = new ApiClient()
const result = await client.firstMethod().then(c => c.secondMethod())

# 7. 控制并发请求

  • Promise.all() 最简单的控制并发,但是请求出错会导致该组无返回值
  • Promise.allSettled()解决了 Promise.all()的问题,但是却存在慢接口阻塞后续请求,且浪费其余并发位置的问题
  • 通过维护一个运行池,当运行池中有请求完成时便从等待队列中取一个心情求入池执行,直到所有的请求都入池
  • 介绍了社区的 p-limit 库的使用方法和实现原理

# 7.1 p-limit

import plimit from 'p-limit'

const limit = plimit(10)

requestList.forEach(async item => {
  const res = await limit(item)
  console.log(res)
})

# 7.2 自定义并发控制

// 运行池
const pool = new Set()

// 等待队列
const waitQueue = []

/**
 * @description: 限制并发数量的请求
 * @param {*} reqFn:请求方法
 * @param {*} max:最大并发数
 */
const request = (reqFn, max) => {
  return new Promise((resolve, reject) => {
    // 判断运行吃是否已满
    const isFull = pool.size >= max

    // 包装的新请求
    const newReqFn = () => {
      reqFn()
        .then(res => {
          resolve(res)
        })
        .catch(err => {
          reject(err)
        })
        .finally(() => {
          // 请求完成后,将该请求从运行池中删除
          pool.delete(newReqFn)
          // 从等待队列中取出一个新请求放入等待运行池执行
          const next = waitQueue.shift()
          if (next) {
            pool.add(next)
            next()
          }
        })
    }

    if (isFull) {
      // 如果运行池已满,则将新的请求放到等待队列中
      waitQueue.push(newReqFn)
    } else {
      // 如果运行池未满,则向运行池中添加一个新请求并执行该请求
      pool.add(newReqFn)
      newReqFn()
    }
  })
}

requestList.forEach(async item => {
  const res = await request(item, 10)
  console.log(res)
})

# 8. 数组去重,数组 mergedArray 根据 id 去重

const uniqueArray = Array.from(
  new Map(mergedArray.map(item => [item.id, item])).values()
)
上次更新: