前端基于订阅发布模式的异步并发控制

40 阅读1分钟
class Event {
  constructor() {
    this.eventMap = {}
  }

  on(type, listener) {
    this.eventMap[type] = (this.eventMap[type] || []).concat(listener)
  }

  emit(type) {
    if (!this.eventMap[type]) return
    this.eventMap[type].forEach((listener) => {
      listener()
    })
  }

  off(type, listener) {
    if (!this.eventMap[type]) return
    this.eventMap[type] = this.eventMap[type].filter((fn) => fn !== listener && fn !== listener.onceHandler)
  }
  once(type, listener) {
    const handler = () => {
      listener()
      this.off(type, handler)
    }
    listener.onceHandler = handler
    this.on(type, handler)
  }
}

class ApiLimitControl extends Event {
  constructor(max) {
    super()
    this.max = max
    this.isRunning = 0
    this.queue = []
    this.id = 0
  }

  register(promiseFn) {
    const id = ++this.id
    const p = new Promise((resolve) => {
      this.once(id, () => {
        Promise.resolve(promiseFn()).then((res) => {
          if (!this.queue.length) {
            this.isRunning--
          } else {
            this.emit(this.queue.shift())
          }
          resolve(res)
        })
        // catch逻辑同上
      })
    })
    if (this.isRunning < this.max) {
      this.isRunning++
      this.emit(id)
    } else {
      this.queue.push(id)
    }
    return p
  }
}

const apiLimitController = new ApiLimitControl(2)
const mockAsync = (timeout) => new Promise((resolve) => {
  setTimeout(() => {
    resolve(timeout)
  }, timeout * 1000)
})
apiLimitController.register(() => mockAsync(1))
apiLimitController.register(() => mockAsync(1))
apiLimitController.register(() => mockAsync(2))