jiefancis / blogs

个人博客,学习笔记(issues)
1 stars 0 forks source link

JS实现一个带并发限制的异步调度器,保证同时运行的任务最多有两个 #4

Open jiefancis opened 3 years ago

jiefancis commented 3 years ago

题意:JS实现一个带并发限制的异步调度器,保证同时运行的任务最多有两个。

题目中提到的关键信息有 并发,异步,同时运行最多两个任务。对关键词的理解如下:

1、并发意味着当任务数量超过最大并发数时,部分任务需要等待;了解js事件循环机制的读者都知道js中的任务队列是储存在事件循环产生的宏(微)任务,等待主线程执行完毕才会从任务队列中取出对应的任务执行,因此并发的实现考虑使用队列的数据结构(定义为pendings)对任务进行管理。

2、异步在js中是promise、setTimeout、async/await generator等实现异步的功能。

3、任务与任务之间花费的时间并不完全相同,为了实现任务之间时间长短的区别,可以使用setTimeout定时触发来模拟延时的功能。

4、最多两个任务需要保证当前执行的队列(runings)中最多同时存在两个任务,只有等待其中一个任务执行结束才会push新的任务。

class Scheduler {
  constructor(){
      this.pendings = []; // 所有注册的任务队列
      this.runings = []; // 当前并发执行的任务队列
      this.capacity = 2; // 最大并发数
      this.id = 0;       // 记录任务下标
      this.runindex = 0; // 当前执行任务的下标,达到过滤的效果
  }
  addTask(fn){
    let item = {
      index: ++this.id, 
      value: fn
    }
    this.pendings.push(item)
    this.push()
  }
  /**
   * @param runindex 过滤,避免同一任务多次触发。
   * 任务进栈执行;条件是当前有任务(pendings)在等待执行并且执行栈中的任务不超过最大并发数。
   * this.runindex实现过滤,两个任务中耗时较长的任务没有执行结束而耗时较长的任务执行结束时,再次执行run()方法则会出现同一个任务多次触发
   */
  push(){
    if (this.runings.length < this.capacity && this.pendings.length){
      let item = this.pendings.splice(0,1)[0]
      this.runindex = item.index
      this.runings.push(item)
      this.run()
    }
  }
  // 执行并发任务;当执行栈为空时退出
  run(){
    let len = this.runings.length;
    if(len) {
      this.runings.forEach((item, index) => {
        if(this.runindex === item.index) {
          item.value().then(() => {
            this.runings.splice(index,1)
            // 当执行栈中出现任务执行结束时,将等待的任务push进栈执行
            this.push()
          })
        }
      })
    }
  }
}
// 实现延时效果,返回promise。保证任务之间耗时长短不一
const timeout = time => new Promise((resolve, reject) => {
  setTimeout(() => resolve(), time)
})
let scheduler = new Scheduler()
const addTask = function(timestamp, order) {
  scheduler.addTask(() => timeout(timestamp).then(() => console.log(`${order} at ${Date.now()}`)))
}
addTask(1000, 1)
addTask(500, 2)
addTask(300,3)
addTask(400,4)
jiefancis commented 3 years ago
  var request = createRequest(3)  
  for(let i = 0; i < 5; i++) {  
      request('https://www.baidu.com').then(v => console.log(v))  
  }
  function createRequest(capacity) {
      let urls = [], runs = [], cur = 0, id = 0;
      function walk(cb){

          if(runs.length < capacity && urls.length) {
              let item = urls.splice(0,1)[0]
              cur = item.id
              runs.push(item)

              runs.forEach((item,index) => {
                  if(item.id === cur) {
                      item.fn().then(res => cb(res))
                      runs.splice(index, 1)
                      walk(cb)
                  }
              })
          }
      }
      return function(url){
          let fn = () => axios({method: 'get', url})
          let map = {id: id++, fn}
          urls.push(map)
          return new Promise((resolve, reject) => {
              walk(resolve)
          })
      }

  }
  function axios({method, url}){
      return new Promise((resolve, reject) => {
          var xhr = new XMLHttpRequest()
          xhr.onreadystatechange = () => {
              if (xhr.readyState === XMLHttpRequest.DONE) {
              if (xhr.status >= 200 && xhr.status < 300) return resolve(xhr.response)
                  reject(xhr)
              }
          }
          xhr.open(method, url)
          xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8')
          xhr.send()
      })

  }
jiefancis commented 3 years ago
 var urls = [

      'https://www.kkkk1000.com/images/getImgData/getImgDatadata.jpg', 
      'https://www.kkkk1000.com/images/getImgData/gray.gif', 
      'https://www.kkkk1000.com/images/getImgData/Particle.gif', 
      'https://www.kkkk1000.com/images/getImgData/arithmetic.png', 
      'https://www.kkkk1000.com/images/getImgData/arithmetic2.gif', 
      'https://www.kkkk1000.com/images/getImgData/getImgDataError.jpg', 
      'https://www.kkkk1000.com/images/getImgData/arithmetic.gif', 
      'https://user-gold-cdn.xitu.io/2018/10/29/166be40ccc434be0?w=600&h=342&f=png&s=122185'
  ];

 function loadImg(url) {

    return new Promise((resolve, reject) => {
        const img = new Image()
        img.onload = function () {
            console.log('一张图片加载完成',url);
            resolve();
        }
        img.onerror = reject
        img.src = url
    })
 };

  var loads = function (limit) {
      var sequence = [].concat(urls);
      var promises = sequence.splice(0, limit).map((url, index) => {
          return loadImg(url).then(() => {
              return index
          })
      })
      return sequence.reduce((p,url,curIndex) => {
          return p.then(() => Promise.race(promises)).then(index => {
              // console.log("每一次是什么值", index, url)
              promises[index] = loadImg(url).then(() => {
                  return index
              })
          })
      }, Promise.resolve())
  }
  loads(3)