使用Promise.any或者Promise.race处理前端高并发问题

65 阅读3分钟

1.先分清几个promise方法的区别:

Promise.any(): 有一个子实例(promise对象)成功才算成功,全部子实例失败才算失败。

Promise.race(): race是竞赛的意思,即看最先的promise子实例是成功还是失败,则它就与最先的子实例状态相同(无论成功与失败)。

Promise.all(): 全部子实例(promise对象)成功才算成功,一个子实例失败就算失败。

Promise.allSettled(): 所有子实例状态都返回结果,不管子实例是成功还是失败,包装实例才执行,不过状态总是变成成功 。

2.使用构造函数的方式创建线程池:

创建一个构造函数,可以传参数limit,callback,其中limit就是我们要限制的并发量,callback是所有promise执行完了之后的回调函数;

自定义并发池pool属性和存储请求地址的urls属性并初始化为[],自定义的limit和callback等于传递进来的对应参数;

class PromisePool{
  constructor(limit, callback){
    this.limit = limit; // limit为每次的并发量
    this.pool = []; //初始化并发池
    this.callback = callback; //所有任务执行完的回调
    this.urls = []; //urls地址为所有的请求地址数组
  }
} 

定义一个运行promise任务的方法,其中用到的this.mockFetch()是用于模拟http请求的一个方法,this.addTask()是往线程池里面添加任务。在task的finially时,使用Promise.any()或者Promise.race()获取线程池任意一个要完成的任务,如果在意必须要获取到一个成功的结果,请使用Promise.any(),如果不在意成功与失败,请使用Promise.race()

// 运行任务
runTask(url){
  console.time('计时');
  let task = this.mockFetch(url);
  // this.pool里面的是promise任务数组
  if(this.pool.length < this.limit) {
    this.pool.push(task);
  }

  task.then((res) => {
    console.log('成功:',res);
    console.timeEnd('计时');
  }).catch((err) => {
    console.log('错误:',err);
    console.timeEnd('计时');
    }).finally(()=>{
      // 利用promise.any或者promise.race方法获取某个任务完成的信号
      let data = Promise.any(this.pool);
    // 每个请求结束后,将该promise请求从并发池移除
      // 成功和失败都要执行移除和往并发池添加新任务的动作
    this.pool = this.pool.filter((ele) => ele != task);
      this.addTask(data);
    if (this.pool.length === 0) {
      this.callback('执行完了');
    }
  })
}

// 再次往线程池添加任务
addTask(task){
  task.finally(res => {
    if (this.urls.length > 0) {
      let url = this.urls.shift();
      // 一有一个任务完成,就再塞下一个
      this.runTask(url);
    }
  });
}

// 模拟http请求
mockFetch(param){
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(param.info%2 === 1){
        resolve(param);
      } else{
        reject(param);
      }
    }, 3000);
  })
}

定义一个初始化方法start,首次运行的时候把线程池全部填满,线程池数组的长度由传入的limit和请求地址长度共同决定:

start(urls){
    this.urls = urls;
    let len = Math.min(this.urls.length, this.limit);
    let initUrl = this.urls.splice(0, len); //切割之后urls还剩len之后的部分
    
    if (initUrl.length > 0) {
      // 首次把线程池塞满
      for (let i = 0; i < initUrl.length; i++) {
        this.runTask(initUrl[i]);
      }
    }
}

完整的代码示例如下:

class PromisePool{
  constructor(limit, callback){
    this.limit = limit; // limit为每次的并发量
    this.pool = []; //初始化并发池
    this.callback = callback; //所有任务执行完的回调
    this.urls = []; //urls地址为所有的请求地址数组
  }
  
  start(urls){
    // 首次初始化并发池
    this.urls = urls;
    let len = Math.min(this.urls.length, this.limit);
    let initUrl = this.urls.splice(0, len); //切割之后urls还剩len之后的部分
    
    if (initUrl.length > 0) {
      // 首次把线程池塞满
      for (let i = 0; i < initUrl.length; i++) {
        this.runTask(initUrl[i]);
      }
    }
  }

  // 运行任务
  runTask(url){
    console.time('计时');
    let task = this.mockFetch(url);
    // this.pool里面的是promise任务数组
    if(this.pool.length < this.limit) {
      this.pool.push(task);
    }

    task.then((res) => {
      console.log('成功:',res);
      console.timeEnd('计时');
    }).catch((err) => {
      console.log('错误:',err);
      console.timeEnd('计时');
    }).finally(()=>{
      // 每个请求结束后,将该promise请求从并发池移除
      this.pool = this.pool.filter((ele) => ele !== task);
      // 利用promise.race或者promise.any方法获取某个执行后的任务
      let data = Promise.race(this.pool);
      // 成功和失败之后都要执行移除和往并发池添加新promise的动作
      this.addTask(data);
       
      if (this.pool.length === 0 && this.urls.length === 0) {
        this.callback('执行完了');
      }
    })
  }

  // 再次往线程池添加任务
  addTask(task){
    task.finally(res => {
      if (this.urls.length > 0) {
        let url = this.urls.shift();
        // 一有一个任务完成,就再塞下一个
        this.runTask(url);
      }
    });
  }

  // 模拟http请求
  mockFetch(param){
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if(param.info%2 === 1){
          resolve(param);
        } else{
          reject(param);
        }
      }, 3000);
    })
  }
}

const pool = new PromisePool(5, (res) => {
  console.log(res)
});

const urls = []
for(let i = 0; i < 100; i++){
  urls.push({
    info: i+1,
    data: (i+1)*100
  });
}

pool.start(urls);

qrcode_for_gh_599d8540a289_258.jpg

扫码关注二师兄微信公众号

文章若有错误,恳请大家指出问题所在,本人不胜感激 。不懂的地方可以评论,我都会一一回复。文章对大家有帮助的话,希望大家能动手点赞鼓励。