前端并发?这次我给你讲彻底

165 阅读2分钟

当提到并发你会想到什么?

没错,就是这位兄弟Promise.all

Promise.all怎么用的

    const createPromiser = delay => {
      return new Promise(resolve => setTimeout(() => resolve(delay), delay))
    }
    
    const p1 = createPromiser(100)
    const p2 = createPromiser(200)
    const p3 = createPromiser(300)
    
    Promise.all([p1,p2,p3]).then(value => {
      console.log(value); // [ 100, 200, 300 ] 按顺序来输出每个promise返回值
    })
    

Promise.all实现

// 验证promise
function isPromise (promise) {
  return promise && typeof promise.then === 'function'
}

Promise.all = function (promises) {
  // Promise.all是个函数,返回一个新的promise 
  return new Promise((resolve,reject) => {
    let le = promises.length
    // 存储每个promise resolve后的value
    let result = new Array(le)
    // 定义个计数器,很重要。只有当计数器 = le时候,所有的promise就执行完成了。可以直接resolve(result)
    let count = 0
    for(let i = 0; i < le; i++) {
       // 取出每一项
      const promise = promises[i]
      // 可能当前项并不是一个promise,这里要做个验证。
      if(isPromise(promise)) {
        promise.then(value => {
          // 闭包记录每个promise的fullfiled状态的值
          result[i] = value
          // 每次执行promose resolve异步回调都要让计数器+1。
          if(++count === le) {
            resolve(result)
          } 
        },reject)
      }else {
       // 闭包记录非promise的值
        result[i] = promise  
        // 这里也是一样,非promise也要让计数器+1
        if(++count === le) {
          resolve(result)
        }
      }
    }
  })

}
  • 从代码里我们可以看出,通过for循环,把所有的promise都一次性执行完成。

假如一个页面要请求100次接口,你会怎么做?

  • 上次请求完成后再请求下一个,一个请求平均时间100ms,那么100个请求就是 100 * 100ms = 10000ms = 10s。这要花费10s的时间才能全部请求完成。在这10s内页面一直处于Loading状态,这是不是用户体验很不好。
// 模拟实现请求
async function requestAll () {
  console.time()
  for(let i = 0; i < 100; i++) {
    await createPromiser(100)
  }
   console.timeEnd() // default: 10608.35400390625 ms
}
requestAll() 
  • 借用Promise.all一次性发送100个请求。虽然可以实现快速的数据获取,但是并发数过大可能会导致服务器压力过大。
async function requestAll () {
  console.time()
  await Promise.all(Array.from({length: 100}, () => createPromiser(100)))
  console.timeEnd() // default: 110.302734375 ms
}
requestAll() 
  • 能不能实现一个方法,能控制并发呢?

并发控制器的实现

class Scheduler {
  constructor(max){
    // 最大并发数
    this.max = max
    // 存储promise resolve的队列
    this.resolves = []
    // 计数器
    this.count = 0
  }
  async add(createPromiser){
    // 当计数器>=最大并发数时候,就中断后面函数的执行。
    if(this.count >= this.max) {
    // 借助于Promise await,只要让当前这个promsie一直处于pending状态就可以实现
      await new Promise(resolve => this.resolves.push(resolve))
    }
    // 每次执行一个promsie任务,计数器就+1
    this.count++
    const result = await createPromiser().then(value => [null,value], (err) => [err,null])
    // 执行完成后,计数器-1
    this.count--
    // 当前promise行完成之后,就从resolves队列中取出一个resolve来执行。
    const resolve = this.resolves.shift()
    resolve && resolve()
    return result
  }
}

// 最大并发数3
const scheduler = new Scheduler(3)
const promises = []
Array.from({length:100}, () => {
  // 这里返回的还是个promise
  // 我们可以使用promise.all来实现
  const p = scheduler.add(() => createPromiser(100))
  promises.push(p)
})

console.time()
Promise.all(promises).then(() => {
  console.timeEnd() // default: 1835.571044921875 ms
})