日常一练——补全实现图片批量预加载方法preloadImage

162 阅读2分钟

1.题目描述

/**
 * 补全实现图片批量预加载方法preloadImage
 * 支持同时加载N张图片,且支持配置整体资源加载超时时间,即:当资源在指定时间内没有全部完成加载(加载失败也算加载完成)时,控制台输出超时提示信息。细节要求:
 * 设定超时时间x毫秒
 * X毫秒内资源没有全部响应完成,控制台输出 "resource load cost over x ms"
 * x毫秒内资源全部响应完成,控制台输出加载失败的资源的错误信思,没有加载失败的,则不输出,如:
 * /a-png load fail
 * /c.png load fail
 */

preloadImage([], n, timeout);

2.举例

preloadImage(
  [
    "./images/1001-800x800.jpg",
    "./images/289-500x1000.jpg",
    "./images/334-500x1000.jpg",
    // "./images/334-.jpg",
    "./images/img.jpg",
    // "./images/img2.jpg",
    "./images/原1.png",
  ],
  3,
  1000
);

//同时加载3张图片,超时时间为1000,超时输出,加载失败输出

3.问题拆解

3.1 首先先把简单的情况拆解出来

先考虑超时情况,无论加载成功还是失败,都不需要关心; 这里可以用`Promise.race()`
async function preloadImage(urls, maxLoadCount, timeout) {
  // 超时Promise
  const maxTimePromise = () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({
          type: "error",
          info: "resource load cost over " + timeout + " ms",
        });
      }, timeout);
    });
  };
  // 加载图片Promise
  const loadImagePromise = () => {
    return new Promise((resolve, reject) => {});
  };

  const res = await Promise.race([maxTimePromise(), loadImagePromise()]);

  res?.info && console.log(res?.info);
}

3.2 然后考虑不超时的情况

这里问题拆解后,就是同时加载 N 张图片资源,全部响应,有失败打印错误信思,无失败的,则不输出。

只需要看loadImagePromise如何实现。

3.2.1 再次拆解,只考虑一张一张加载,且加载成功的情况。

加载图片实际上就是一个个请求,这里先把 urls转化 为 PromiseFn[]

const imgProArr = urls.map((url) => {
  return function () {
    return new Promise((res, rej) => {
      const img = new Image();
      img.src = url;
      img.onload = () => {
        res({ url, type: "success" });
      };
      img.onerror = () => {
        res({ url, type: "fail" });
      };
    });
  };
});
//这里返回的是一个函数数组,例如:开始加载第一张图片就可以拿出第一个函数执行 imgProArr[0]()
const loadImagePromise = () => {
  return new Promise((resolve, reject) => {
    const errList = []; //加载失败的图片
    let curIndex = 0; //当前加载的图片
    let hasSuccessCount = 0; //加载完成的图片数量(包括失败)
    const goFn = async () => {
      const res = await imgProArr?.[curIndex++]();
      hasSuccessCount++;
      if (res.type === "fail") {
        errList.push(res.url);
      }

      if (curIndex < urls.length) {
        goFn();
      }
      if (hasSuccessCount === urls.length) {
        resolve({
          type: "success",
          info: errList.map((i) => i + " load fail").join("\n"),
        });
      }
    };
    goFn();
  });
};

3.2.2 同时加载 N 张图片

这里只需要在开始时多次执行goFn()即可

for (let i = 0; i < maxLoadCount; i++) {
  goFn();
}

4.Code 实现

整体 code 如下

const rootDom = document.getElementById("root");
async function preloadImage(urls, maxLoadCount, timeout) {

  const maxTimePromise = () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({
          type: "error",
          info: "resource load cost over " + timeout + " ms",
        });
      }, timeout);
    });
  };
  
  // urls 转化成 promise[] 需要时取出来使用
  const imgProArr = urls.map((url) => {
    return function () {
      return new Promise((res, rej) => {
        const img = new Image();
        img.src = url;
        img.onload = () => {
          rootDom.appendChild(img);
          res({ url, type: "success" });
        };
        img.onerror = () => {
          res({ url, type: "fail" });
        };
      });
    };
  });

  const loadImagePromise = () => {
    return new Promise((resolve, reject) => {
      const errList = [];
      let curIndex = 0;
      let hasSuccessCount = 0;
      const goFn = async () => {
        const res = await imgProArr?.[curIndex++]();
        hasSuccessCount++;
        if (res.type === "fail") {
          errList.push(res.url);
        }

        if (curIndex < urls.length) {
          goFn();
        }
        if (hasSuccessCount === urls.length) {
          resolve({
            type: "success",
            info: errList.map((i) => i + " load fail").join("\n"),
          });
        }
      };

      for (let i = 0; i < maxLoadCount; i++) {
        goFn();
      }
    });
  };
  const res = await Promise.race([maxTimePromise(), loadImagePromise()]);
  res?.info && console.log(res?.info);
}

preloadImage(
  [
    "./images/1001-800x800.jpg",
    "./images/289-500x1000.jpg",
    "./images/334-500x1000.jpg",
    "./images/334-.jpg",
    "./images/img.jpg",
    "./images/img2.jpg",
  ],
  3,
  1000
);