Jansiel Notes

前端性能优化:使用 Web Workers 实现轮询

痛点

轮询是一种常见的客户端与服务器端通信的方法,通过定期发送 HTTP 请求来获取最新数据。这种方法虽然简单易实现,但在某些情况下会对主线程性能产生负面影响,比如需要兼容性能较差的手机和需要高性能页面不影响主进程时。

在面对性能较差的手机时,我们需要尽量的减少主线程的 CPU 占用,因为主线程需要进行 UI 渲染,用户操作。主线程被频繁占用可能导致页面响应变慢,影响用户体验。例如,用户可能会感觉到页面滚动不流畅,按钮点击反应迟缓等。

因此 我想结合之前说过的 webworker 来解决,利用 Web Workers 将轮询任务移出主线程。
Jansiel_Essay_1718279023434
Web Worker 为浏览器提供了多线程处理能力,允许在后台线程执行脚本,避免了长时间运行的脚本导致的页面失去响应。这意味着,像轮询这样的耗时任务可以委托给 Worker 线程处理,保证了用户界面的流畅性。

关于 webworker 可以参考我之前写的这篇 《你不要命啦?动态创建 Web Worker 还能这样用啊!》

实现步骤

  1. 创建 Worker 脚本:首先,定义一个包含轮询逻辑的 JavaScript 代码字符串,并将其封装进一个 Blob 对象。此 Blob 对象随后被用来创建一个新的 Worker 实例。
 1const blob = new Blob(
 2  [
 3    `
 4      let requestCount = 0
 5        // 处理收到的消息
 6        self.onmessage = function(event) {
 7            ...
 8        };
 9        // 开始轮询函数
10        function startPolling(interval, url, data, headers) {
11            ...
12        }
13    `,
14  ],
15  { type: "application/javascript" }
16);
17
18const worker = new Worker(URL.createObjectURL(blob));
19
  1. 定义轮询函数:在 Worker 脚本中,定义 startPolling 函数,它负责执行实际的 HTTP 请求并设置下一次轮询的定时器。这样,一旦接收到主线程发来的启动命令,Worker 就会开始周期性地调用该函数。
 1function startPolling(interval, url, data, headers) {
 2  function poll() {
 3    fetch(url, {
 4      method: "POST",
 5      headers: new Headers(headers),
 6      body: JSON.stringify({ ...data }),
 7    })
 8      .then((response) => {
 9        return response.json();
10      })
11      .then((res) => {
12        // 将请求结果发送回主线程
13        self.postMessage({ res, requestCount: requestCount++ });
14      })
15      .catch((error) => {
16        console.log("Request failed:", error);
17      });
18
19    // 调用自身以实现持续轮询
20    setTimeout(poll, interval);
21  }
22
23  // 立即执行一次
24  poll();
25}
26
  1. 配置与启动轮询:在主线程中,创建 Worker 实例后,通过 postMessage 方法向 Worker 发送初始化信息,包括轮询间隔、请求的 URL、请求头和数据。Worker 接收到这些信息后,开始执行轮询逻辑。
 1worker.postMessage({
 2  type: "start",
 3  interval: 5000,
 4  url: "http://192.168.110.145:18200/gateway/xxxxxx",
 5  headers: {
 6    "content-type": "application/json; charset=utf-8",
 7    time: Base64.encode(serverTimeStamp().toString()),
 8    accountId: Base64.encode(tipWords.userId) || "",
 9    authToken: TOKEN,
10    requestId: getRandomNumberFn(),
11  },
12  data: {
13    secretCode: RsaAndAes.encrypt(Key),
14    encryptedData: RsaAndAes.encryptAes(saveData),
15    // userId: tipWords.userId
16  },
17}); // 每5秒轮询一次
18
  1. 处理响应与错误:Worker 内部的轮询函数通过 fetch API 发起请求,并处理响应或错误。成功获取到数据后,通过 self.postMessage 将结果传回主线程,以便进一步解密和处理。

  2. 定义终止条件,停止 webworker。

1worker.onmessage = function (event) {
2  if (event.data.requestCount > 2) {
3    worker.terminate();
4  }
5  // 业务逻辑
6};
7

实现代码

 1// pollWorker.js
 2import { Base64 } from 'js-base64';
 3import RsaAndAes from '~/composables/RsaAndAes';
 4import { getRandomNumberFn } from '~/composables/baseRequest';
 5
 6export function createWorker() {
 7  const blob = new Blob(
 8    [
 9      `
10let requestCount = 0;
11// 处理收到的消息
12self.onmessage = function (event) {
13  if (event.data.type === "start") {
14    // 开始轮询
15    const interval = event.data.interval;
16    startPolling(interval, event.data.url, event.data.data, event.data.headers);
17  }
18};
19
20// 开始轮询函数
21function startPolling(interval, url, data, headers) {
22  function poll() {
23    fetch(url, {
24      method: "POST",
25      headers: new Headers(headers),
26      body: JSON.stringify({ ...data }),
27    })
28      .then((response) => {
29        return response.json();
30      })
31      .then((res) => {
32        // 将请求结果发送回主线程
33        self.postMessage({ res, requestCount: requestCount++ });
34      })
35      .catch((error) => {
36        console.log("Request failed:", error);
37      });
38
39    // 调用自身以实现持续轮询
40    setTimeout(poll, interval);
41  }
42
43  // 立即执行一次
44  poll();
45}
46    `
47    ],
48    { type: 'application/javascript' }
49  );
50
51  const worker = new Worker(URL.createObjectURL(blob));
52  // 将post函数传递给WebWorker
53
54  const TOKEN = '';
55  const saveData = JSON.parse(JSON.stringify({} || {}));
56  const config = {};
57  const interDomainName = '';
58  const ENV = '';
59  const nodeEnv = '';
60  const Key = ''; //存储公钥
61
62  // 发送开始消息给WebWorker,传递轮询间隔
63  worker.postMessage({
64    type: 'start',
65    interval: 5000,
66    url: 'http://192.168.110.145:18200/gateway/xxxxxxx',
67    headers: {
68      'content-type': 'application/json; charset=utf-8',
69      partnerId: 'MTAy',
70      time: '',
71      accountId: '',
72      countries: '',
73      authToken: TOKEN,
74      requestId: ''
75    },
76    data: {
77      secretCode: RsaAndAes.encrypt(Key),
78      encryptedData: RsaAndAes.encryptAes(saveData)
79    }
80  }); // 每5秒轮询一次
81  return worker;
82}
83

总结

使用 Web Workers 实现轮询能够显著改善前端性能,尤其是在移动设备或低性能设备上。通过将轮询任务移至后台线程,主线程得以专注于用户界面的渲染和交互,提高了应用的响应速度和流畅度。

优势:

  • 将耗时的网络请求和数据处理任务从主线程移至 Web Worker,减少了对主线程的占用,使用户界面更流畅。
  • 主线程的空闲时间增加,有助于提升滚动、点击等用户操作的响应速度,避免页面卡顿现象。
  • ️ Worker 自身沙盒特性使项目更加安全可靠