请求池

本文最后更新于:3 个月前

问题

有8个图片资源的url,已经存储在数组urls中。

urls类似于[‘https://image1.png', ‘https://image2.png', ….]

而且已经有一个函数function loadImg,输入一个url链接,返回一个Promise,该Promise在图片下载完成的时候resolve,下载失败则reject。

但有一个要求,任何时刻同时下载的链接数量不可以超过3个。
请写一段代码实现这个需求,要求尽可能快速地将所有图片下载完成。

var urls = [
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png",
];
function loadImg(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = function() {
      console.log("一张图片加载完成");
      resolve(img);
    };
    img.onerror = function() {
    	reject(new Error('Could not load image at' + url));
    };
    img.src = url;
  });

想法

在实际项目中,经常会出现问题中的情况,我们需要请求很多资源或者进行很多请求,但是并发数过多会使得其他请求进入排队状态,从而影响页面的展示。这里我们就有必要使用一个限制请求的请求池维护请求。通过维护一个请求队列,在执行完一个请求后及时调用新的请求,维持时刻都有请求正在进行,并且最多只有上限数量的请求

代码

最开始想到的方法,比较复杂


function createRequest({ list = [], pool = 5 }) {
  //TODO
  let promises = [];
  function queue(index = 0) {
    //执行到最后一个边界
    if (index === list.length) {
      return Promise.all(promises);
    }
    //取出一个请求
    const promise = list[index]();

    //执行进队列
    promises.push(doRequest(promise, index));

    //如果进入队列的长度到了上限,拿到队列中最快的请求
    let race = Promise.resolve();
    if (promises.length >= pool) {
      race = Promise.race(promises);
    }
    //在最快执行完的promise中调用下一次请求添加入队列
    return race.then(() => queue(index + 1));
  }

  //做一次请求
  function doRequest(promise, index) {
    const p = new Promise(async (re, rj) => {
      try {
        await promise;
        re(index);
      } catch (e) {
        console.log(index);
        rj(e);
      }
    }).then(() => {
      //执行完删除队列里的自己
      let index = promises.indexOf(p);
      index !== -1 && promises.splice(index, 1);
    });
    return p;
  }

  return queue();
}


let p = function () {
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      console.log("1000");
      resolve(1000);
    }, 1000);
  });
};
let p1 = function () {
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      console.log("7000");
      resolve(7000);
    }, 7000);
  });
};
let p2 = function () {
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      console.log("3000");
      reject(3000);
    }, 3000);
  });
};
let p3 = function () {
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      console.log("4000");
      resolve(4000);
    }, 4000);
  });
};
let p4 = function () {
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      console.log("5000");
      resolve(5000);
    }, 3000);
  });
};
let p5 = function () {
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      console.log("6000");
      resolve(6000);
    }, 3000);
  });
};

console.time("time");
createRequest({ list: [p, p1, p2, p3, p4, p5], pool: 4 }).then((res) => {
  console.timeEnd("time");
  console.log(res);
});

urls使用的网络上的图片,更加直观

let urls = [
        "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png",
        "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png",
        "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png",
        "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png",
        "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png",
        "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png",
        "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png",
        "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png",
    ];
    function loadImg(url) {
        return new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = function () {
                console.log("一张图片加载完成");
                resolve(img);
            };
            img.onerror = function () {
                reject(new Error('Could not load image at' + url));
            };
            img.src = url;
            img.width = '100'
            img.height = '200'
        });
    }
    function createRequest(urls,limit) {
        let results = [];
        //先拿出最大数量的请求放进请求池
        let promises = urls.splice(0, limit).map((element, index) => {
            return loadImg(element).then(res => {
                results.push(res)
                return index;
            },res=>{
              results.push(res)
              return index;
            })
        });
        let queue = i=>{
          //使用race去拿出最快完成的一项
            return Promise.race(promises).then(res => {
              //替换掉请求池中完成的一项
                promises[res] = loadImg(urls[i]).then(_=> {
                    results.push(_)
                    return res;
                },_=>{
                  results.push(_)
                  return res;
                })
            },res=>{
              //替换掉请求池中完成的一项
                promises[res] = loadImg(urls[i]).then(_=> {
                    results.push(_)
                    return res;
                },_=>{
                  results.push(_)
                  return res;
                })
            }).then(_=>{
              //如果排队的请求还有,进入下一个请求的操作
                if (i < urls.length - 1) {
                    return queue(i + 1)
                }else{
                //请求没有了,就使用all把最后三个直接全部结束
                    return Promise.allSettled(promises).then(_=>result);
                }
            });
        }
       return queue(0)
    }
    createRequest(urls,3).then(res=>{
        console.log('请求结束',res)
    })

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!