구체적인 웹 작업자 사용 사례

이전 모듈에서는 웹 작업자 개요를 살펴보았습니다. 웹 작업자는 자바스크립트를 기본 스레드에서 별도의 웹 작업자 스레드로 이동하여 입력 응답성을 개선할 수 있습니다. 이렇게 하면 기본 스레드에 직접 액세스할 필요가 없는 작업이 있을 때 웹사이트의 다음 페인트와의 상호작용 (INP)을 개선할 수 있습니다. 그러나 개요만으로는 충분하지 않으며 이 모듈에서는 웹 작업자에 대한 구체적인 사용 사례를 제공합니다.

이미지에서 Exif 메타데이터를 제거해야 하는 웹사이트가 이러한 사용 사례 중 하나일 수 있습니다. 실제로 Flickr와 같은 웹사이트에서는 사용자가 호스팅하는 이미지에 관한 기술 세부정보(예: 색심도, 카메라 제조업체 및 모델, 기타 데이터)를 알아보기 위해 Exif 메타데이터를 확인할 수 있는 방법을 제공합니다.

그러나 이미지를 가져오고, 이미지를 ArrayBuffer로 변환하고, Exif 메타데이터를 추출하는 로직은 전적으로 기본 스레드에서 실행되면 비용이 많이 들 수 있습니다. 다행히 웹 작업자 범위를 사용하면 이 작업을 기본 스레드에서 실행할 수 있습니다. 그런 다음 웹 작업자의 메시징 파이프라인을 사용하여 Exif 메타데이터가 기본 스레드에 HTML 문자열로 다시 전송되어 사용자에게 표시됩니다.

웹 워커가 없는 기본 스레드의 모습

먼저 웹 작업자 없이 이 작업을 할 때 기본 스레드가 어떻게 표시되는지 관찰합니다. 이렇게 하려면 다음 단계를 따르세요.

  1. Chrome에서 새 탭을 열고 DevTools를 엽니다.
  2. 성능 패널을 엽니다.
  3. https://exif-worker.glitch.me/without-worker.html으로 이동합니다.
  4. 성능 패널에서 DevTools 창 오른쪽 상단에 있는 Record를 클릭합니다.
  5. 필드에 이 이미지 링크 또는 Exif 메타데이터가 포함된 다른 링크 중 하나를 붙여넣고 Get that JPEG! 버튼을 클릭합니다.
  6. 인터페이스에 Exif 메타데이터가 채워지면 Record를 다시 클릭하여 기록을 중지합니다.
전적으로 기본 스레드에서 발생하는 이미지 메타데이터 추출기 앱의 활동을 보여주는 성능 프로파일러 여기에는 상당한 두 가지 작업이 있습니다. 하나는 가져오기를 실행하여 요청된 이미지를 가져와 디코딩하고, 다른 하나는 이미지에서 메타데이터를 추출합니다.
이미지 메타데이터 추출기 앱의 기본 스레드 활동입니다. 모든 활동은 기본 스레드에서 발생합니다.

래스터라이저 스레드 등 있을 수 있는 다른 스레드를 제외하고 앱의 모든 것은 기본 스레드에서 발생합니다. 기본 스레드에서는 다음과 같은 결과가 발생합니다.

  1. 이 양식은 입력을 받고 fetch 요청을 전달하여 Exif 메타데이터가 포함된 이미지의 초기 부분을 가져옵니다.
  2. 이미지 데이터가 ArrayBuffer로 변환됩니다.
  3. exif-reader 스크립트는 이미지에서 Exif 메타데이터를 추출하는 데 사용됩니다.
  4. 메타데이터를 스크래핑하여 HTML 문자열을 생성한 후 메타데이터 뷰어를 채웁니다.

이제 이 점을 웹 worker를 사용하여 동일한 동작을 구현하는 것과 대조해 보세요.

웹 작업자가 있는 기본 스레드의 모습

지금까지 기본 스레드의 JPEG 파일에서 Exif 메타데이터를 추출하는 방법을 살펴봤습니다. 이제 웹 작업자가 함께 사용될 때 어떻게 표시되는지 살펴보겠습니다.

  1. Chrome에서 다른 탭을 열고 DevTools를 엽니다.
  2. 성능 패널을 엽니다.
  3. https://exif-worker.glitch.me/with-worker.html로 이동합니다.
  4. 성능 패널에서 DevTools 창 오른쪽 상단에 있는 기록 버튼을 클릭합니다.
  5. 입력란에 이 이미지 링크를 붙여넣고 JPEG 가져오기 버튼을 클릭합니다.
  6. 인터페이스가 Exif 메타데이터로 채워지면 기록 버튼을 다시 클릭하여 기록을 중지합니다.
기본 스레드와 웹 작업자 스레드에서 모두 발생하는 이미지 메타데이터 추출기 앱의 활동을 보여주는 성능 프로파일러 기본 스레드에 여전히 긴 작업이 있지만 이미지 가져오기/디코딩 및 메타데이터 추출이 전적으로 웹 작업자 스레드에서 발생하므로 작업이 훨씬 더 짧습니다. 유일한 기본 스레드 작업은 웹 작업자와의 데이터 전달과 관련이 있습니다.
이미지 메타데이터 추출기 앱의 기본 스레드 활동. 대부분의 작업이 실행되는 웹 작업자 스레드가 추가로 있습니다.

이것이 바로 웹 작업자의 힘입니다. 기본 스레드에서 모든 작업을 실행하는 대신 메타데이터 뷰어를 HTML로 채우는 작업을 제외한 모든 작업은 별도의 스레드에서 이루어집니다. 즉, 기본 스레드가 다른 작업을 할 수 있도록 해제됩니다.

여기서 가장 큰 장점은 웹 워커를 사용하지 않는 이 앱의 버전과 달리 exif-reader 스크립트가 기본 스레드가 아닌 웹 워커 스레드에 로드된다는 것입니다. 즉, exif-reader 스크립트를 다운로드, 파싱, 컴파일하는 비용은 기본 스레드에서 발생합니다.

이제 이 모든 것을 가능하게 하는 웹 작업자 코드를 자세히 살펴보겠습니다.

웹 작업자 코드 살펴보기

웹 작업자가 만들어내는 차이를 보는 것만으로는 충분하지 않으며, 적어도 이 경우에는 코드가 어떻게 보이는지 이해하여 웹 작업자 범위에서 가능한 작업을 파악할 수 있습니다.

웹 작업자가 그림을 시작하기 전에 발생해야 하는 기본 스레드 코드로 시작합니다.

// 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';
});

이 코드는 기본 스레드에서 실행되며 웹 작업자에게 이미지 URL을 전송하는 양식을 설정합니다. 여기에서 웹 작업자 코드는 외부 exif-reader 스크립트를 로드하는 importScripts 문으로 시작된 후 기본 스레드에 대한 메시지 파이프라인을 설정합니다.

// 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 파일 URL이 포함된 양식을 제출할 때 URL이 웹 워커에 도착하도록 메시지 파이프라인을 설정합니다. 거기에서 다음 비트 코드는 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('')
      });
    });
  });
});

약간 읽기 쉬울 수도 있지만 이는 웹 워커의 경우 상당히 복잡한 사용 사례이기도 합니다. 그러나 그 결과는 이 사용 사례뿐만 아니라 노력할 가치가 있습니다. fetch 호출 격리 및 응답 처리, 기본 스레드를 차단하지 않고 대량의 데이터 처리와 같은 모든 종류의 작업에 웹 작업자를 사용할 수 있습니다. 시작에 불과합니다.

웹 애플리케이션의 성능을 개선할 때는 웹 작업자 환경에서 합리적으로 실행할 수 있는 모든 방법을 생각해 보세요. 상당한 이점을 얻을 수 있고 웹사이트의 사용자 환경을 전반적으로 개선할 수 있습니다.