Web Worker 的具体用例

<ph type="x-smartling-placeholder">

在上一单元中,我们介绍了 Web Worker 概览。Web Worker 可以 通过将 JavaScript 从主线程移到 单独的网络工作器线程,这有助于改善网站的互动体验 进行下一次绘制 (INP) 主线程。不过,仅仅提供概览是不够的,在本单元中, 还提供了具体的 Web Worker 用例。

例如,网站需要将 Exif 元数据从 这并不是一个笼统的概念。事实上,Flickr 等网站 用户可通过一种方式查看 Exif 元数据,从而了解有关 例如色深、相机品牌和型号等, 数据。

不过,用于提取图片并将其转换为 ArrayBuffer 的逻辑: 而提取 Exif 元数据的成本可能 在主线程上执行幸运的是,Web Worker 作用域允许完成这项工作, 从主线程中移除然后,使用 Web Worker 的消息传递流水线, Exif 元数据作为 HTML 字符串传回主线程,并且 显示给用户

没有 Web Worker 时主线程是什么样的

首先,观察一下在不使用 Web Worker。为此,请执行以下步骤:

  1. 在 Chrome 中打开一个新标签页,然后打开其开发者工具。
  2. 打开性能面板
  3. 前往 https://exif-worker.glitch.me/without-worker.html
  4. 在性能面板中,点击 “开发者工具”窗格
  5. 粘贴此图片链接,或您自己选择的其他包含 Exif 的图片链接 元数据 - 在该字段中点击 Get that JPEG!(获取 JPEG!)按钮。
  6. 当界面填充了 Exif 元数据后,再次点击 Record(记录)以 停止录制。
。 <ph type="x-smartling-placeholder">
</ph> 性能分析器,显示图片元数据提取器应用的 activity 完全发生在主线程上。有两个耗时较长的任务:一个运行提取来获取请求的图片并解码,另一个从图片中提取元数据。
图片元数据提取器应用中的主线程 activity。请注意, 活动发生在主线程上

请注意,除了可能存在的其他线程(例如光栅化处理程序)之外, 等等 - 应用中的一切操作都在主线程中进行。在主页面 会发生以下情况:

  1. 表单接受输入并分派 fetch 请求以获取初始 部分。
  2. 图片数据会转换为 ArrayBuffer
  3. exif-reader 脚本用于从 图片。
  4. 从元数据抓取过程中构建 HTML 字符串,然后填充 元数据查看器。

接下来,我们对比一下,实现相同行为,但使用的是 员工!

Web Worker 的主线程实现方式

现在,您已经了解了从 JPEG 文件,我们来看看 worker 包括:

  1. 在 Chrome 中打开另一个标签页,然后打开其开发者工具。
  2. 打开性能面板
  3. 前往 https://exif-worker.glitch.me/with-worker.html
  4. 在性能面板中,点击右上角的记录按钮
  5. 该图片链接粘贴到相应字段中,然后点击获取该图片!按钮。
  6. 当界面填充了 Exif 元数据后,点击 record 按钮 即可停止录制。
。 <ph type="x-smartling-placeholder">
</ph> 显示图片元数据提取器应用同时在主线程和 Web 工作器线程上进行的活动的性能分析器。虽然主线程上仍然有耗时较长的任务,但任务时间要短很多,因为图像提取/解码和元数据提取完全在 Web Worker 线程上进行。唯一的主线程工作涉及将数据传入和传出 Web Worker。
图片元数据提取器应用中的主线程 activity。请注意, 一个额外的 Web 工作器线程,用于完成大部分工作。

这就是 Web Worker 的强大功能。你不用在主分支上做任何事情, 除使用 HTML 填充元数据查看器外,其他所有操作都是在 单独的线程。这意味着主线程被释放出来以执行其他工作。

也许这个版本的最大优势在于 不使用 Web Worker,因此 exif-reader 脚本不会在主函数上加载 而是在 Web 工作器线程上执行这意味着 下载、解析和编译 exif-reader 脚本的操作是在 主线程。

现在,深入了解让这一切成为可能的 Web Worker 代码!

Web Worker 代码一览

仅仅看到 Web Worker 带来的差异是不够的, 至少能理解代码是什么样子,这样您就能知道 在 Web Worker 作用域内运行。

<ph type="x-smartling-placeholder">

从需要在 Web Worker 运行之前执行的主线程代码开始 输入图片:

// scripts.js

// Register the Exif reader web worker:
const exifWorker = new Worker('/js/with-worker/exif-worker.js');

// We have to send image requests through this proxy due to CORS limitations:
const imageFetchPrefix = 'https://res.cloudinary.com/demo/image/fetch/';

// Necessary elements we need to select:
const imageFetchPanel = document.getElementById('image-fetch');
const imageExifDataPanel = document.getElementById('image-exif-data');
const exifDataPanel = document.getElementById('exif-data');
const imageInput = document.getElementById('image-url');

// What to do when the form is submitted.
document.getElementById('image-form').addEventListener('submit', event => {
  // Don't let the form submit by default:
  event.preventDefault();

  // Send the image URL to the web worker on submit:
  exifWorker.postMessage(`${imageFetchPrefix}${imageInput.value}`);
});

// This listens for the Exif metadata to come back from the web worker:
exifWorker.addEventListener('message', ({ data }) => {
  // This populates the Exif metadata viewer:
  exifDataPanel.innerHTML = data.message;
  imageFetchPanel.style.display = 'none';
  imageExifDataPanel.style.display = 'block';
});

此代码在主线程上运行,它会设置表单以将图片网址发送到 Web Worker。然后,Web Worker 代码以 importScripts 开头 语句,用于加载外部 exif-reader 脚本,然后设置 将消息发送到主线程:

// exif-worker.js

// Import the exif-reader script:
importScripts('/js/with-worker/exifreader.js');

// Set up a messaging pipeline to send the Exif data to the `window`:
self.addEventListener('message', ({ data }) => {
  getExifDataFromImage(data).then(status => {
    self.postMessage(status);
  });
});

这段 JavaScript 代码会设置消息传递管道,这样一来,当用户 在提交带有 JPEG 文件网址的表单时,网址到达 Web Worker。 接下来,下面这段代码从 JPEG 文件中提取 Exif 元数据, 构建一个 HTML 字符串,并将该 HTML 发送回 window,最终 显示给用户:

// Takes a blob to transform the image data into an `ArrayBuffer`:
// NOTE: these promises are simplified for readability, and don't include
// rejections on failures. Check out the complete web worker code:
// https://glitch.com/edit/#!/exif-worker?path=js%2Fwith-worker%2Fexif-worker.js%3A10%3A5
const readBlobAsArrayBuffer = blob => new Promise(resolve => {
  const reader = new FileReader();

  reader.onload = () => {
    resolve(reader.result);
  };

  reader.readAsArrayBuffer(blob);
});

// Takes the Exif metadata and converts it to a markup string to
// display in the Exif metadata viewer in the DOM:
const exifToMarkup = exif => Object.entries(exif).map(([exifNode, exifData]) => {
  return `
    <details>
      <summary>
        <h2>${exifNode}</h2>
      </summary>
      <p>${exifNode === 'base64' ? `<img src="data:image/jpeg;base64,${exifData}">` : typeof exifData.value === 'undefined' ? exifData : exifData.description || exifData.value}</p>
    </details>
  `;
}).join('');

// Fetches a partial image and gets its Exif data
const getExifDataFromImage = imageUrl => new Promise(resolve => {
  fetch(imageUrl, {
    headers: {
      // Use a range request to only download the first 64 KiB of an image.
      // This ensures bandwidth isn't wasted by downloading what may be a huge
      // JPEG file when all that's needed is the metadata.
      'Range': `bytes=0-${2 ** 10 * 64}`
    }
  }).then(response => {
    if (response.ok) {
      return response.clone().blob();
    }
  }).then(responseBlob => {
    readBlobAsArrayBuffer(responseBlob).then(arrayBuffer => {
      const tags = ExifReader.load(arrayBuffer, {
        expanded: true
      });

      resolve({
        status: true,
        message: Object.values(tags).map(tag => exifToMarkup(tag)).join('')
      });
    });
  });
});

读起来有点用,但对于 Web Worker,这也是一个相当复杂的用例。 然而,这些成果是值得的,而不仅仅局限于此用例。 您可以使用 Web Worker 执行各种操作,例如隔离 fetch 调用 和处理响应、处理大量数据而不阻塞 主线程,这仅供新手使用。

在改进 Web 应用的性能时,请开始考虑 可在 Web Worker 环境中合理完成的任何任务。好处可能是 并为您的网站带来更出色的整体用户体验。