并行加载技术实战分享 从原理到优化全面解析
我的写法,亲测靠谱
在前端优化中,并行加载是一个非常实用的技术。它可以帮助我们提高资源加载速度,提升用户体验。我在实际项目中用过很多次,并行加载,积累了一些实战经验,今天就来分享一下。
首先,我一般会使用 Promise.all 来实现并行加载。这个方法的好处是简单直接,而且代码可读性很高。下面是我的一个典型用法:
const urls = [
'https://jztheme.com/api/data1',
'https://jztheme.com/api/data2',
'https://jztheme.com/api/data3'
];
const fetchData = async (url) => {
const response = await fetch(url);
return response.json();
};
Promise.all(urls.map(fetchData))
.then(results => {
console.log('所有数据加载完成', results);
})
.catch(error => {
console.error('加载出错', error);
});
这样写的好处是,所有的请求会同时发起,当所有请求都成功返回时,Promise.all 会将结果合并成一个数组。如果有一个请求失败,整个 Promise.all 会立即进入 catch 状态,你可以在这个回调里处理错误。
这几种错误写法,别再踩坑了
说完了正确的写法,再说说常见的错误写法。这些错误我也踩过不少坑,希望你不要再犯同样的错误。
错误1:使用 for 循环
很多人一开始会想到用 for 循环来实现并行加载,但这种方法实际上是串行的。比如下面这种写法:
const urls = [
'https://jztheme.com/api/data1',
'https://jztheme.com/api/data2',
'https://jztheme.com/api/data3'
];
const results = [];
for (let i = 0; i < urls.length; i++) {
const response = await fetch(urls[i]);
results.push(await response.json());
}
console.log('所有数据加载完成', results);
这种写法虽然看起来很直观,但实际上每个请求都是依次执行的。也就是说,第一个请求完成后才会发起第二个请求,这样就完全失去了并行加载的意义。
错误2:忽略错误处理
还有一种常见的错误是没有做好错误处理。比如下面这种写法:
const urls = [
'https://jztheme.com/api/data1',
'https://jztheme.com/api/data2',
'https://jztheme.com/api/data3'
];
const promises = urls.map(url => fetch(url));
Promise.all(promises)
.then(responses => Promise.all(responses.map(response => response.json())))
.then(results => {
console.log('所有数据加载完成', results);
});
这种写法的问题在于,如果任何一个请求失败了,整个 Promise.all 会直接进入 catch 状态,但你并没有处理这个错误。结果就是,一旦有一个请求出错,你的代码就无法继续执行下去,甚至连错误信息都看不到。
实际项目中的坑
在实际项目中,使用并行加载时还会遇到一些其他的坑。这里我总结了几点需要注意的地方:
注意1:控制并发数
有时候,如果你要加载的资源非常多,无限制地并发请求可能会导致浏览器卡顿或者服务器压力过大。这时,你需要控制并发数。我一般会用 async/await 和 Promise.all 的结合来实现这一点。
const urls = [
'https://jztheme.com/api/data1',
'https://jztheme.com/api/data2',
'https://jztheme.com/api/data3',
'https://jztheme.com/api/data4',
'https://jztheme.com/api/data5'
];
const maxConcurrency = 3;
const queue = urls.slice();
async function processQueue() {
while (queue.length > 0) {
const activePromises = [];
for (let i = 0; i 0; i++) {
const url = queue.shift();
activePromises.push(fetch(url).then(response => response.json()));
}
await Promise.all(activePromises);
}
}
processQueue().then(results => {
console.log('所有数据加载完成', results);
});
这样写的好处是,每次只会发起固定数量的请求(这里是 3 个),等到这批请求完成后再发起下一批。这样可以有效避免一次性发起太多请求导致的问题。
注意2:合理设置超时时间
在并行加载时,合理设置超时时间也很重要。如果没有设置超时时间,某个请求一直不返回,会导致整个页面加载变慢。我一般会在 fetch 里加上超时处理。
const urls = [
'https://jztheme.com/api/data1',
'https://jztheme.com/api/data2',
'https://jztheme.com/api/data3'
];
const fetchDataWithTimeout = (url, timeout = 5000) => {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error('请求超时'));
}, timeout);
fetch(url)
.then(response => {
clearTimeout(timer);
resolve(response.json());
})
.catch(error => {
clearTimeout(timer);
reject(error);
});
});
};
Promise.all(urls.map(url => fetchDataWithTimeout(url)))
.then(results => {
console.log('所有数据加载完成', results);
})
.catch(error => {
console.error('加载出错', error);
});
这样写的好处是,如果某个请求超过 5 秒还没有返回,就会被自动取消,并抛出一个超时错误。这样可以保证页面不会因为某个请求卡住而迟迟不能加载完毕。
总结
以上是我个人总结的一些关于并行加载的最佳实践和踩坑经验。希望对你有所帮助。如果你有更好的方案或建议,欢迎在评论区交流。这个技巧的拓展用法还有很多,后续我会继续分享这类博客。

暂无评论