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
);