תרחיש לדוגמה לשימוש ב-Web Worker

במודול האחרון, קיבלתם סקירה כללית על Web Workers. ה-Web Workers יכולים לשפר את מהירות התגובה לקלט על ידי העברת JavaScript מהשרשור הראשי לשרשורי Web Worker נפרדים. כך אפשר לשפר את מהירות התגובה לאינטראקציה באתר (INP) כשמבצעים פעולות שלא דורשות גישה ישירה לשרשור הראשי. עם זאת, סקירה כללית לבדה לא מספיקה, ובמודול הזה מוצג תרחיש שימוש קונקרטי ב-Web Worker.

מקרה שימוש כזה יכול להיות אתר שצריך להסיר מטא-נתונים של Exif מתמונה – זה לא רעיון מופרך. למעשה, אתרים כמו Flickr מציעים למשתמשים דרך לצפות במטא-נתוני Exif כדי לקבל פרטים טכניים על התמונות שהם מארחים, כמו עומק הצבע, יצרן ודגם המצלמה ונתונים אחרים.

עם זאת, הלוגיקה לאחזור תמונה, להמרה שלה ל-ArrayBuffer ולחילוץ מטא-נתוני Exif עלולה להיות יקרה אם היא מתבצעת כולה בשרשור הראשי. למזלנו, ההיקף של web worker מאפשר לבצע את העבודה הזו מחוץ ל-thread הראשי. לאחר מכן, באמצעות צינור העברת ההודעות של ה-Web Worker, מטא-נתוני ה-Exif מועברים חזרה ל-thread הראשי כמחרוזת HTML, ומוצגים למשתמש.

איך נראה ה-thread הראשי בלי Web Worker

קודם כל, נתבונן במראה של ה-thread הראשי כשמבצעים את הפעולה הזו בלי web worker. כדי לעשות זאת:

  1. פותחים כרטיסייה חדשה ב-Chrome ופותחים את כלי הפיתוח שלה.
  2. פותחים את חלונית הביצועים.
  3. עוברים אל https://chrome.dev/learn-performance-exif-worker/without-worker.html.
  4. בחלונית הביצועים, לוחצים על Record (הקלטה) בפינה השמאלית העליונה של חלונית כלי הפיתוח.
  5. מדביקים את הקישור הזה לתמונה – או קישור אחר שתבחרו שמכיל מטא נתונים של Exif – בשדה ולוחצים על הלחצן Get that JPEG!‎.
  6. אחרי שמטא-נתוני Exif יאכלסו את הממשק, לוחצים שוב על Record כדי להפסיק את ההקלטה.
כלי פרופיל הביצועים שמציג את הפעילות של אפליקציית חילוץ המטא-נתונים של התמונה, שמתרחשת כולה בשרשור הראשי. יש שתי משימות ארוכות ומשמעותיות – אחת שמפעילה אחזור כדי לקבל את התמונה המבוקשת ולפענח אותה, ואחת שמחלצת את המטא-נתונים מהתמונה.
פעילות בשרשור הראשי באפליקציה לחילוץ מטא-נתונים של תמונות. שימו לב שכל הפעילות מתרחשת בשרשור הראשי.

חשוב לזכור – חוץ מ-threads אחרים שעשויים להיות קיימים, כמו threads של rasterizer וכו' – כל מה שקורה באפליקציה מתרחש ב-thread הראשי. בשרשור הראשי, קורה הדבר הבא:

  1. הטופס מקבל את הקלט ושולח בקשת fetch כדי לקבל את החלק הראשוני של התמונה שמכיל את מטא-נתוני ה-Exif.
  2. נתוני התמונה מומרים ל-ArrayBuffer.
  3. הסקריפט exif-reader משמש לחילוץ מטא-נתוני Exif מהתמונה.
  4. המטא-נתונים נסרקים כדי ליצור מחרוזת HTML, שמוזנת לאחר מכן לכלי להצגת מטא-נתונים.

עכשיו נשווה את זה להטמעה של אותו התנהגות – אבל באמצעות web worker!

איך נראה ה-thread הראשי עם Web Worker

עכשיו, אחרי שראיתם איך נראה חילוץ מטא-נתוני Exif מקובץ JPEG בשרשור הראשי, בואו נראה איך זה נראה כשמשתמשים ב-Web Worker:

  1. פותחים כרטיסייה נוספת ב-Chrome ופותחים את כלי הפיתוח שלה.
  2. פותחים את חלונית הביצועים.
  3. עוברים אל https://chrome.dev/learn-performance-exif-worker/with-worker.html.
  4. בחלונית הביצועים, לוחצים על לחצן ההקלטה בפינה השמאלית העליונה של חלונית כלי הפיתוח.
  5. מדביקים את הקישור הזה לתמונה בשדה ולוחצים על הלחצן Get that JPEG!‎ (קבלת קובץ JPEG).
  6. אחרי שממשק המשתמש מתמלא במטא-נתוני Exif, לוחצים שוב על לחצן ההקלטה כדי להפסיק את ההקלטה.
כלי פרופיל הביצועים מציג את הפעילות של אפליקציית חילוץ המטא-נתונים של התמונה שמתרחשת גם בשרשור הראשי וגם בשרשור של Web Worker. עדיין יש משימות ארוכות בשרשור הראשי, אבל הן קצרות משמעותית. אחזור התמונה, הפענוח שלה וחילוץ המטא-נתונים מתבצעים באופן מלא בשרשור של Web Worker. העבודה היחידה בשרשור הראשי היא העברת נתונים אל ומ-Web Worker.
פעילות השרשור הראשי באפליקציה לחילוץ מטא-נתונים של תמונות. שימו לב שיש שרשור נוסף של Web Worker שבו מתבצע רוב העבודה.

זו העוצמה של Web Worker. במקום לבצע הכול בשרשור הראשי, כל הפעולות חוץ מאכלוס מציג המטא-נתונים ב-HTML מתבצעות בשרשור נפרד. המשמעות היא שה-thread הראשי מתפנה לביצוע עבודות אחרות.

היתרון הכי גדול כאן הוא שבניגוד לגרסה של האפליקציה הזו שלא משתמשת ב-Web Worker, הסקריפט exif-reader לא נטען בשרשור הראשי, אלא בשרשור של ה-Web Worker. המשמעות היא שהעלות של הורדה, ניתוח והידור של סקריפט exif-reader מתרחשת מחוץ לשרשור הראשי.

עכשיו נצלול לקוד של Web Worker שמאפשר את כל זה.

סקירה של קוד ה-Web Worker

לא מספיק לראות את ההבדל ש-Web Worker יוצר, חשוב גם להבין – לפחות במקרה הזה – איך הקוד הזה נראה כדי לדעת מה אפשר לעשות במסגרת Web Worker.

מתחילים עם הקוד של ה-thread הראשי שצריך להתרחש לפני ש-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';
});

הקוד הזה פועל בשרשור הראשי, ומגדיר את הטופס לשליחת כתובת ה-URL של התמונה אל ה-Web Worker. מכאן, קוד ה-Web Worker מתחיל בהצהרה importScripts שמעמיסה את הסקריפט החיצוני exif-reader, ואז מגדירה את צינור העברת ההודעות ל-thread הראשי:

// 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 הזה מגדיר את צינור ההודעות כך שכשהמשתמש שולח את הטופס עם כתובת URL לקובץ JPEG, כתובת ה-URL מגיעה ל-Web Worker. מכאן, קטע הקוד הבא מחלץ את מטא-נתוני ה-Exif מקובץ ה-JPEG, יוצר מחרוזת 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://chrome.dev/learn-performance-exif-worker/js/with-worker/exif-worker.js
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 Workers. עם זאת, התוצאות שוות את המאמץ, והן לא מוגבלות רק לתרחיש השימוש הזה. אפשר להשתמש ב-Web Workers לכל מיני דברים, כמו בידוד של קריאות ל-fetch ועיבוד של תגובות, עיבוד של כמויות גדולות של נתונים בלי לחסום את ה-main thread – וזה רק חלק מהדברים שאפשר לעשות.

כשמשפרים את הביצועים של אפליקציות אינטרנט, כדאי לחשוב על כל מה שאפשר לעשות בהקשר של Web Worker. השיפורים יכולים להיות משמעותיים, ויכולים להוביל לחוויית משתמש טובה יותר באתר.