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

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

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

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

איך נראה ה-thread הראשי בלי worker

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

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

שימו לב שמלבד שרשורים אחרים שעשויים להיות קיימים, כמו שרשורים של רסטר וכו', כל מה שבאפליקציה מתרחש ב-thread הראשי. ב-thread הראשי קורים הדברים הבאים:

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

עכשיו נשווה זאת עם יישום של אותה התנהגות - אלא באמצעות עובד אינטרנט!

איך נראה ה-thread הראשי עם worker

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

  1. פותחים כרטיסייה אחרת ב-Chrome ופותחים את כלי הפיתוח שלה.
  2. פותחים את חלונית הביצועים.
  3. עוברים לכתובת https://exif-worker.glitch.me/with-worker.html.
  4. בחלונית הביצועים, לוחצים על לחצן ההקלטה בפינה השמאלית העליונה של חלונית כלי הפיתוח.
  5. מדביקים את הקישור לתמונה הזו בשדה ולוחצים על הלחצן Get this JPEG!.
  6. לאחר שהממשק יאוכלס במטא-נתונים של Exif, לחצו שוב על לחצן ההקלטה כדי להפסיק את ההקלטה.
הכלי לניתוח ביצועים מציג את הפעילות של האפליקציה לחילוץ מטא-נתונים של תמונות שמתרחשת ב-thread הראשי וגם ב-thread של worker. ב-thread הראשי יש עדיין משימות ארוכות, אבל הן קצרות יותר באופן משמעותי, מאחר שאחזור/פענוח של התמונה וחילוץ המטא-נתונים מתבצעים במלואם בשרשור של Web worker. תהליך העבודה היחיד ב-thread הראשי כרוך בהעברת נתונים אל עובד האינטרנט וממנו.
הפעילות של ה-thread הראשי באפליקציה לחילוץ המטא-נתונים של התמונות. חשוב לשים לב שיש thread נוסף של Web worker שבו מתבצעת רוב העבודה.

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

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

עכשיו ניכנס לעומק לקוד של Web worker שמאפשר את כל זה!

מבט בקוד של Worker

לא מספיק לראות את ההבדל שעובד אינטרנט עושה, הוא גם עוזר להבין – לפחות במקרה הזה – איך הקוד הזה נראה כדי שתדעו מה אפשרי בתחום של 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';
});

הקוד הזה פועל ב-thread הראשי ומגדיר את הטופס לשליחת כתובת ה-URL של התמונה לעובד האינטרנט. משם, הקוד של 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://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 לכל מיני פעולות, כמו בידוד קריאות ל-fetch ועיבוד תשובות, עיבוד כמויות גדולות של נתונים בלי לחסום את ה-thread הראשי – וזה רק בהתחלה.

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