并行加载技术实战分享 从原理到优化全面解析

Good“润茁 优化 阅读 2,603
赞 58 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

在前端优化中,并行加载是一个非常实用的技术。它可以帮助我们提高资源加载速度,提升用户体验。我在实际项目中用过很多次,并行加载,积累了一些实战经验,今天就来分享一下。

并行加载技术实战分享 从原理到优化全面解析

首先,我一般会使用 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/awaitPromise.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 秒还没有返回,就会被自动取消,并抛出一个超时错误。这样可以保证页面不会因为某个请求卡住而迟迟不能加载完毕。

总结

以上是我个人总结的一些关于并行加载的最佳实践和踩坑经验。希望对你有所帮助。如果你有更好的方案或建议,欢迎在评论区交流。这个技巧的拓展用法还有很多,后续我会继续分享这类博客。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论