ไทย

สำรวจรูปแบบขั้นสูงสำหรับ JavaScript Module Workers เพื่อเพิ่มประสิทธิภาพการประมวลผลเบื้องหลัง เพิ่มสมรรถนะของเว็บแอปพลิเคชันและประสบการณ์ผู้ใช้สำหรับผู้ชมทั่วโลก

JavaScript Module Workers: การเรียนรู้รูปแบบการประมวลผลเบื้องหลังสำหรับภูมิทัศน์ดิจิทัลระดับโลก

ในโลกที่เชื่อมต่อถึงกันในปัจจุบัน เว็บแอปพลิเคชันถูกคาดหวังมากขึ้นเรื่อยๆ ที่จะมอบประสบการณ์ที่ราบรื่น ตอบสนองได้ดี และมีประสิทธิภาพ โดยไม่คำนึงถึงตำแหน่งของผู้ใช้หรือความสามารถของอุปกรณ์ ความท้าทายที่สำคัญในการบรรลุเป้าหมายนี้คือการจัดการงานที่ต้องใช้การคำนวณสูงโดยไม่ทำให้ส่วนติดต่อผู้ใช้ (UI) หลักค้าง นี่คือจุดที่ Web Workers ของ JavaScript เข้ามามีบทบาท โดยเฉพาะอย่างยิ่ง การมาถึงของ JavaScript Module Workers ได้ปฏิวัติวิธีที่เราจัดการกับการประมวลผลเบื้องหลัง โดยนำเสนอวิธีการที่แข็งแกร่งและเป็นโมดูลมากขึ้นในการย้ายงานออกไปทำที่อื่น

คู่มือฉบับสมบูรณ์นี้จะเจาะลึกถึงพลังของ JavaScript Module Workers สำรวจรูปแบบการประมวลผลเบื้องหลังต่างๆ ที่สามารถเพิ่มประสิทธิภาพและประสบการณ์ผู้ใช้ของเว็บแอปพลิเคชันของคุณได้อย่างมาก เราจะครอบคลุมแนวคิดพื้นฐาน เทคนิคขั้นสูง และให้ตัวอย่างที่ใช้งานได้จริงโดยคำนึงถึงมุมมองระดับโลก

วิวัฒนาการสู่ Module Workers: ก้าวข้าม Web Workers พื้นฐาน

ก่อนที่จะเจาะลึกถึง Module Workers สิ่งสำคัญคือต้องเข้าใจรุ่นก่อนหน้าของมัน: Web Workers แบบดั้งเดิม (Traditional Web Workers) ช่วยให้คุณสามารถรันโค้ด JavaScript ในเธรดเบื้องหลังที่แยกต่างหาก ป้องกันไม่ให้มันบล็อกเธรดหลัก สิ่งนี้มีค่าอย่างยิ่งสำหรับงานต่างๆ เช่น:

อย่างไรก็ตาม Web Workers แบบดั้งเดิมมีข้อจำกัดบางประการ โดยเฉพาะอย่างยิ่งในเรื่องการโหลดและการจัดการโมดูล สคริปต์ของเวิร์กเกอร์แต่ละตัวเป็นไฟล์ขนาดใหญ่ไฟล์เดียว ทำให้ยากต่อการนำเข้าและจัดการไลบรารีที่ต้องใช้ (dependencies) ภายในบริบทของเวิร์กเกอร์ การนำเข้าไลบรารีหลายตัวหรือการแบ่งตรรกะที่ซับซ้อนออกเป็นโมดูลขนาดเล็กที่นำกลับมาใช้ใหม่ได้นั้นยุ่งยาก และมักจะนำไปสู่ไฟล์เวิร์กเกอร์ที่ใหญ่เกินความจำเป็น

Module Workers แก้ไขข้อจำกัดเหล่านี้โดยอนุญาตให้เวิร์กเกอร์เริ่มต้นการทำงานโดยใช้ ES Modules ซึ่งหมายความว่าคุณสามารถนำเข้าและส่งออกโมดูลได้โดยตรงภายในสคริปต์เวิร์กเกอร์ของคุณ เช่นเดียวกับที่คุณทำในเธรดหลัก สิ่งนี้นำมาซึ่งข้อได้เปรียบที่สำคัญ:

แนวคิดหลักของ JavaScript Module Workers

โดยหัวใจแล้ว Module Worker ทำงานคล้ายกับ Web Worker แบบดั้งเดิม ความแตกต่างหลักอยู่ที่วิธีการโหลดและเรียกใช้งานสคริปต์ของเวิร์กเกอร์ แทนที่จะระบุ URL โดยตรงไปยังไฟล์ JavaScript คุณจะระบุ URL ของ ES Module

การสร้าง Module Worker พื้นฐาน

นี่คือตัวอย่างพื้นฐานของการสร้างและใช้งาน Module Worker:

worker.js (สคริปต์ของโมดูลเวิร์กเกอร์):


// worker.js

// ฟังก์ชันนี้จะถูกเรียกใช้งานเมื่อเวิร์กเกอร์ได้รับข้อความ
self.onmessage = function(event) {
  const data = event.data;
  console.log('Message received in worker:', data);

  // ทำงานบางอย่างในเบื้องหลัง
  const result = data.value * 2;

  // ส่งผลลัพธ์กลับไปยังเธรดหลัก
  self.postMessage({ result: result });
};

console.log('Module Worker เริ่มต้นทำงานแล้ว');

main.js (สคริปต์ของเธรดหลัก):


// main.js

// ตรวจสอบว่ารองรับ Module Workers หรือไม่
if (window.Worker) {
  // สร้าง Module Worker ใหม่
  // หมายเหตุ: พาธควรชี้ไปยังไฟล์โมดูล (ซึ่งมักจะมีนามสกุล .js)
  const myWorker = new Worker('./worker.js', { type: 'module' });

  // รอรับข้อความจากเวิร์กเกอร์
  myWorker.onmessage = function(event) {
    console.log('Message received from worker:', event.data);
  };

  // ส่งข้อความไปยังเวิร์กเกอร์
  myWorker.postMessage({ value: 10 });

  // คุณยังสามารถจัดการข้อผิดพลาดได้
  myWorker.onerror = function(error) {
    console.error('Worker error:', error);
  };
} else {
  console.log('เบราว์เซอร์ของคุณไม่รองรับ Web Workers');
}

หัวใจสำคัญคือตัวเลือก `{ type: 'module' }` เมื่อสร้างอินสแตนซ์ `Worker` ซึ่งจะบอกเบราว์เซอร์ให้ปฏิบัติต่อ URL ที่ให้มา (`./worker.js`) ในฐานะ ES Module

การสื่อสารกับ Module Workers

การสื่อสารระหว่างเธรดหลักและ Module Worker (และในทางกลับกัน) เกิดขึ้นผ่านข้อความ ทั้งสองเธรดสามารถเข้าถึงเมธอด `postMessage()` และตัวจัดการเหตุการณ์ `onmessage` ได้

สำหรับการสื่อสารที่ซับซ้อนหรือบ่อยครั้งขึ้น อาจพิจารณารูปแบบเช่น message channels หรือ shared workers แต่สำหรับกรณีการใช้งานส่วนใหญ่ `postMessage` ก็เพียงพอแล้ว

รูปแบบการประมวลผลเบื้องหลังขั้นสูงด้วย Module Workers

ตอนนี้เรามาสำรวจวิธีใช้ประโยชน์จาก Module Workers สำหรับงานประมวลผลเบื้องหลังที่ซับซ้อนยิ่งขึ้น โดยใช้รูปแบบที่สามารถนำไปใช้กับฐานผู้ใช้ทั่วโลกได้

รูปแบบที่ 1: คิวงาน (Task Queues) และการกระจายงาน

สถานการณ์ทั่วไปคือความต้องการทำงานอิสระหลายๆ อย่างพร้อมกัน แทนที่จะสร้างเวิร์กเกอร์แยกสำหรับแต่ละงาน (ซึ่งอาจไม่มีประสิทธิภาพ) คุณสามารถใช้เวิร์กเกอร์เดียว (หรือกลุ่มของเวิร์กเกอร์) พร้อมกับคิวงาน

worker.js:


// worker.js

let taskQueue = [];
let isProcessing = false;

async function processTask(task) {
  console.log(`Processing task: ${task.type}`);
  // จำลองการทำงานที่ใช้การคำนวณสูง
  await new Promise(resolve => setTimeout(resolve, task.duration || 1000));
  return `Task ${task.type} completed.`;
}

async function runQueue() {
  if (isProcessing || taskQueue.length === 0) {
    return;
  }

  isProcessing = true;
  const currentTask = taskQueue.shift();

  try {
    const result = await processTask(currentTask);
    self.postMessage({ status: 'success', taskId: currentTask.id, result: result });
  } catch (error) {
    self.postMessage({ status: 'error', taskId: currentTask.id, error: error.message });
  } finally {
    isProcessing = false;
    runQueue(); // ประมวลผลงานถัดไป
  }
}

self.onmessage = function(event) {
  const { type, data, taskId } = event.data;

  if (type === 'addTask') {
    taskQueue.push({ id: taskId, ...data });
    runQueue();
  } else if (type === 'processAll') {
    // พยายามประมวลผลงานใดๆ ที่อยู่ในคิวทันที
    runQueue();
  }
};

console.log('Task Queue Worker เริ่มต้นทำงานแล้ว');

main.js:


// main.js

if (window.Worker) {
  const taskWorker = new Worker('./worker.js', { type: 'module' });
  let taskIdCounter = 0;

  taskWorker.onmessage = function(event) {
    console.log('Worker message:', event.data);
    if (event.data.status === 'success') {
      // จัดการเมื่องานเสร็จสมบูรณ์
      console.log(`Task ${event.data.taskId} finished with result: ${event.data.result}`);
    } else if (event.data.status === 'error') {
      // จัดการข้อผิดพลาดของงาน
      console.error(`Task ${event.data.taskId} failed: ${event.data.error}`);
    }
  };

  function addTaskToWorker(taskData) {
    const taskId = ++taskIdCounter;
    taskWorker.postMessage({ type: 'addTask', data: taskData, taskId: taskId });
    console.log(`Added task ${taskId} to queue.`);
    return taskId;
  }

  // ตัวอย่างการใช้งาน: เพิ่มงานหลายรายการ
  addTaskToWorker({ type: 'image_resize', duration: 1500 });
  addTaskToWorker({ type: 'data_fetch', duration: 2000 });
  addTaskToWorker({ type: 'data_process', duration: 1200 });

  // เรียกการประมวลผลเมื่อจำเป็น (เช่น เมื่อคลิกปุ่ม)
  // taskWorker.postMessage({ type: 'processAll' });

} else {
  console.log('เบราว์เซอร์นี้ไม่รองรับ Web Workers');
}

ข้อควรพิจารณาในระดับโลก: เมื่อกระจายงาน ควรคำนึงถึงภาระของเซิร์ฟเวอร์และความหน่วงของเครือข่าย สำหรับงานที่เกี่ยวข้องกับ API ภายนอกหรือข้อมูล ควรเลือกตำแหน่งหรือภูมิภาคของเวิร์กเกอร์ที่ลดเวลา ping สำหรับกลุ่มเป้าหมายของคุณ ตัวอย่างเช่น หากผู้ใช้ของคุณส่วนใหญ่อยู่ในเอเชีย การโฮสต์แอปพลิเคชันและโครงสร้างพื้นฐานของเวิร์กเกอร์ใกล้กับภูมิภาคเหล่านั้นจะสามารถปรับปรุงประสิทธิภาพได้

รูปแบบที่ 2: การย้ายการคำนวณหนักๆ ไปทำด้วยไลบรารี

JavaScript สมัยใหม่มีไลบรารีที่มีประสิทธิภาพสำหรับงานต่างๆ เช่น การวิเคราะห์ข้อมูล, machine learning, และการสร้างภาพข้อมูลที่ซับซ้อน Module Workers เหมาะอย่างยิ่งสำหรับการรันไลบรารีเหล่านี้โดยไม่กระทบต่อ UI

สมมติว่าคุณต้องการทำการรวมข้อมูลที่ซับซ้อนโดยใช้ไลบรารีสมมติชื่อ `data-analyzer` คุณสามารถนำเข้าไลบรารีนี้ได้โดยตรงใน Module Worker ของคุณ

data-analyzer.js (ตัวอย่างโมดูลไลบรารี):


// data-analyzer.js

export function aggregateData(data) {
  console.log('Aggregating data in worker...');
  // จำลองการรวมข้อมูลที่ซับซ้อน
  let sum = 0;
  for (let i = 0; i < data.length; i++) {
    sum += data[i];
    // เพิ่มดีเลย์เล็กน้อยเพื่อจำลองการคำนวณ
    // ในสถานการณ์จริง นี่คือการคำนวณจริงๆ
    for(let j = 0; j < 1000; j++) { /* ดีเลย์ */ }
  }
  return { total: sum, count: data.length };
}

analyticsWorker.js:


// analyticsWorker.js

import { aggregateData } from './data-analyzer.js';

self.onmessage = function(event) {
  const { dataset } = event.data;
  if (!dataset) {
    self.postMessage({ status: 'error', message: 'No dataset provided' });
    return;
  }

  try {
    const result = aggregateData(dataset);
    self.postMessage({ status: 'success', result: result });
  } catch (error) {
    self.postMessage({ status: 'error', message: error.message });
  }
};

console.log('Analytics Worker เริ่มต้นทำงานแล้ว');

main.js:


// main.js

if (window.Worker) {
  const analyticsWorker = new Worker('./analyticsWorker.js', { type: 'module' });

  analyticsWorker.onmessage = function(event) {
    console.log('Analytics result:', event.data);
    if (event.data.status === 'success') {
      document.getElementById('results').innerText = `Total: ${event.data.result.total}, Count: ${event.data.result.count}`;
    } else {
      document.getElementById('results').innerText = `Error: ${event.data.message}`;
    }
  };

  // เตรียมชุดข้อมูลขนาดใหญ่ (จำลอง)
  const largeDataset = Array.from({ length: 10000 }, (_, i) => i + 1);

  // ส่งข้อมูลไปยังเวิร์กเกอร์เพื่อประมวลผล
  analyticsWorker.postMessage({ dataset: largeDataset });

} else {
  console.log('ไม่รองรับ Web Workers');
}

HTML (สำหรับแสดงผลลัพธ์):


<div id="results">กำลังประมวลผลข้อมูล...</div>

ข้อควรพิจารณาในระดับโลก: เมื่อใช้ไลบรารี ควรตรวจสอบให้แน่ใจว่าได้รับการปรับให้เหมาะสมเพื่อประสิทธิภาพสูงสุด สำหรับผู้ชมต่างประเทศ ควรพิจารณาการแปลภาษา (localization) สำหรับผลลัพธ์ใดๆ ที่ผู้ใช้ต้องเห็นซึ่งสร้างโดยเวิร์กเกอร์ แม้ว่าโดยทั่วไปแล้วผลลัพธ์ของเวิร์กเกอร์จะถูกประมวลผลแล้วแสดงผลโดยเธรดหลักซึ่งเป็นผู้จัดการเรื่องการแปลภาษา

รูปแบบที่ 3: การซิงโครไนซ์ข้อมูลแบบเรียลไทม์และการแคช

Module Workers สามารถรักษาการเชื่อมต่อแบบถาวร (เช่น WebSockets) หรือดึงข้อมูลเป็นระยะๆ เพื่อให้แคชในเครื่องเป็นปัจจุบันอยู่เสมอ ซึ่งช่วยให้ผู้ใช้ได้รับประสบการณ์ที่รวดเร็วและตอบสนองได้ดียิ่งขึ้น โดยเฉพาะในภูมิภาคที่อาจมีความหน่วงสูงไปยังเซิร์ฟเวอร์หลักของคุณ

cacheWorker.js:


// cacheWorker.js

let cache = {};
let websocket = null;

function setupWebSocket() {
  // แทนที่ด้วย WebSocket endpoint ของคุณ
  const wsUrl = 'wss://your-realtime-api.example.com/data';
  websocket = new WebSocket(wsUrl);

  websocket.onopen = () => {
    console.log('WebSocket connected.');
    // ขอข้อมูลเริ่มต้นหรือสมัครรับข้อมูล
    websocket.send(JSON.stringify({ action: 'subscribe', topic: 'updates' }));
  };

  websocket.onmessage = (event) => {
    try {
      const message = JSON.parse(event.data);
      console.log('Received WS message:', message);
      if (message.type === 'update') {
        cache[message.key] = message.value;
        // แจ้งเตือนเธรดหลักเกี่ยวกับการอัปเดตแคช
        self.postMessage({ type: 'cache_update', key: message.key, value: message.value });
      }
    } catch (e) {
      console.error('Failed to parse WebSocket message:', e);
    }
  };

  websocket.onerror = (error) => {
    console.error('WebSocket error:', error);
    // พยายามเชื่อมต่อใหม่อีกครั้งหลังจากดีเลย์
    setTimeout(setupWebSocket, 5000);
  };

  websocket.onclose = () => {
    console.log('WebSocket disconnected. Reconnecting...');
    setTimeout(setupWebSocket, 5000);
  };
}

self.onmessage = function(event) {
  const { type, data, key } = event.data;

  if (type === 'init') {
    // อาจจะดึงข้อมูลเริ่มต้นจาก API หาก WS ยังไม่พร้อม
    // เพื่อความง่าย เราจะใช้ WS ที่นี่
    setupWebSocket();
  } else if (type === 'get') {
    const cachedValue = cache[key];
    self.postMessage({ type: 'cache_response', key: key, value: cachedValue });
  } else if (type === 'set') {
    cache[key] = data;
    self.postMessage({ type: 'cache_update', key: key, value: data });
    // ส่งการอัปเดตไปยังเซิร์ฟเวอร์หากจำเป็น
    if (websocket && websocket.readyState === WebSocket.OPEN) {
      websocket.send(JSON.stringify({ action: 'update', key: key, value: data }));
    }
  }
};

console.log('Cache Worker initialized.');

// ทางเลือก: เพิ่มโลจิกการล้างข้อมูลหากเวิร์กเกอร์ถูกยกเลิก
self.onclose = () => {
  if (websocket) {
    websocket.close();
  }
};

main.js:


// main.js

if (window.Worker) {
  const cacheWorker = new Worker('./cacheWorker.js', { type: 'module' });

  cacheWorker.onmessage = function(event) {
    console.log('Cache worker message:', event.data);
    if (event.data.type === 'cache_update') {
      console.log(`Cache updated for key: ${event.data.key}`);
      // อัปเดตองค์ประกอบ UI หากจำเป็น
    }
  };

  // เริ่มต้นเวิร์กเกอร์และการเชื่อมต่อ WebSocket
  cacheWorker.postMessage({ type: 'init' });

  // หลังจากนั้น ขอข้อมูลที่แคชไว้
  setTimeout(() => {
    cacheWorker.postMessage({ type: 'get', key: 'userProfile' });
  }, 3000); // รอสักครู่เพื่อให้ข้อมูลเริ่มต้นซิงค์

  // สำหรับการตั้งค่า
  setTimeout(() => {
    cacheWorker.postMessage({ type: 'set', key: 'userSettings', data: { theme: 'dark' } });
  }, 5000);

} else {
  console.log('Web Workers are not supported.');
}

ข้อควรพิจารณาในระดับโลก: การซิงโครไนซ์แบบเรียลไทม์เป็นสิ่งสำคัญสำหรับแอปพลิเคชันที่ใช้ในเขตเวลาที่แตกต่างกัน ควรตรวจสอบให้แน่ใจว่าโครงสร้างพื้นฐานเซิร์ฟเวอร์ WebSocket ของคุณมีการกระจายอยู่ทั่วโลกเพื่อให้การเชื่อมต่อมีความหน่วงต่ำ สำหรับผู้ใช้ในภูมิภาคที่มีอินเทอร์เน็ตไม่เสถียร ควรใช้ตรรกะการเชื่อมต่อใหม่ที่แข็งแกร่งและกลไกสำรอง (เช่น การสอบถามข้อมูลเป็นระยะๆ หาก WebSocket ล้มเหลว)

รูปแบบที่ 4: การผสานรวมกับ WebAssembly

สำหรับงานที่ต้องการประสิทธิภาพสูงอย่างยิ่ง โดยเฉพาะงานที่เกี่ยวข้องกับการคำนวณทางตัวเลขหนักๆ หรือการประมวลผลภาพ WebAssembly (Wasm) สามารถให้ประสิทธิภาพใกล้เคียงกับระดับเนทีฟได้ Module Workers เป็นสภาพแวดล้อมที่ยอดเยี่ยมในการรันโค้ด Wasm โดยแยกออกจากเธรดหลัก

สมมติว่าคุณมีโมดูล Wasm ที่คอมไพล์จาก C++ หรือ Rust (เช่น `image_processor.wasm`)

imageProcessorWorker.js:


// imageProcessorWorker.js

let imageProcessorModule = null;

async function initializeWasm() {
  try {
    // นำเข้าโมดูล Wasm แบบไดนามิก
    // พาธ './image_processor.wasm' ต้องสามารถเข้าถึงได้
    // คุณอาจต้องกำหนดค่าเครื่องมือ build ของคุณเพื่อจัดการการนำเข้า Wasm
    const response = await fetch('./image_processor.wasm');
    const buffer = await response.arrayBuffer();
    const module = await WebAssembly.instantiate(buffer, {
      // นำเข้าฟังก์ชันโฮสต์หรือโมดูลที่จำเป็นที่นี่
      env: {
        log: (value) => console.log('Wasm Log:', value),
        // ตัวอย่าง: ส่งผ่านฟังก์ชันจากเวิร์กเกอร์ไปยัง Wasm
        // เรื่องนี้ซับซ้อน ข้อมูลมักจะถูกส่งผ่านหน่วยความจำที่ใช้ร่วมกัน (Shared Memory - ArrayBuffer)
      }
    });
    imageProcessorModule = module.instance.exports;
    console.log('WebAssembly module loaded and instantiated.');
    self.postMessage({ status: 'wasm_ready' });
  } catch (error) {
    console.error('Error loading or instantiating Wasm:', error);
    self.postMessage({ status: 'wasm_error', message: error.message });
  }
}

self.onmessage = async function(event) {
  const { type, imageData, width, height } = event.data;

  if (type === 'process_image') {
    if (!imageProcessorModule) {
      self.postMessage({ status: 'error', message: 'Wasm module not ready.' });
      return;
    }

    try {
      // สมมติว่าฟังก์ชัน Wasm ต้องการพอยน์เตอร์ไปยังข้อมูลรูปภาพและขนาด
      // สิ่งนี้ต้องการการจัดการหน่วยความจำอย่างระมัดระวังกับ Wasm
      // รูปแบบทั่วไปคือการจัดสรรหน่วยความจำใน Wasm, คัดลอกข้อมูล, ประมวลผล, แล้วคัดลอกกลับ

      // เพื่อความง่าย สมมติว่า imageProcessorModule.process รับข้อมูลไบต์ดิบของรูปภาพ
      // และส่งคืนไบต์ที่ประมวลผลแล้ว
      // ในสถานการณ์จริง คุณจะใช้ SharedArrayBuffer หรือส่งผ่าน ArrayBuffer

      const processedImageData = imageProcessorModule.process(imageData, width, height);

      self.postMessage({ status: 'success', processedImageData: processedImageData });
    } catch (error) {
      console.error('Wasm image processing error:', error);
      self.postMessage({ status: 'error', message: error.message });
    }
  }
};

// เริ่มต้น Wasm เมื่อเวิร์กเกอร์เริ่มทำงาน
initializeWasm();

main.js:


// main.js

if (window.Worker) {
  const imageWorker = new Worker('./imageProcessorWorker.js', { type: 'module' });
  let isWasmReady = false;

  imageWorker.onmessage = function(event) {
    console.log('Image worker message:', event.data);
    if (event.data.status === 'wasm_ready') {
      isWasmReady = true;
      console.log('Image processing is ready.');
      // ตอนนี้คุณสามารถส่งรูปภาพไปประมวลผลได้แล้ว
    } else if (event.data.status === 'success') {
      console.log('Image processed successfully.');
      // แสดงรูปภาพที่ประมวลผลแล้ว (event.data.processedImageData)
    } else if (event.data.status === 'error') {
      console.error('Image processing failed:', event.data.message);
    }
  };

  // ตัวอย่าง: สมมติว่าคุณมีไฟล์รูปภาพที่ต้องประมวลผล
  // ดึงข้อมูลรูปภาพ (เช่น ในรูปแบบ ArrayBuffer)
  fetch('./sample_image.png')
    .then(response => response.arrayBuffer())
    .then(arrayBuffer => {
      // โดยปกติคุณจะแยกข้อมูลรูปภาพ ความกว้าง ความสูง ที่นี่
      // สำหรับตัวอย่างนี้ เราจะจำลองข้อมูลขึ้นมา
      const dummyImageData = new Uint8Array(1000);
      const imageWidth = 10;
      const imageHeight = 10;

      // รอจนกว่าโมดูล Wasm จะพร้อมก่อนส่งข้อมูล
      const sendImage = () => {
        if (isWasmReady) {
          imageWorker.postMessage({
            type: 'process_image',
            imageData: dummyImageData, // ส่งเป็น ArrayBuffer หรือ Uint8Array
            width: imageWidth,
            height: imageHeight
          });
        } else {
          setTimeout(sendImage, 100);
        }
      };
      sendImage();
    })
    .catch(error => {
      console.error('Error fetching image:', error);
    });

} else {
  console.log('Web Workers are not supported.');
}

ข้อควรพิจารณาในระดับโลก: WebAssembly ช่วยเพิ่มประสิทธิภาพอย่างมีนัยสำคัญ ซึ่งมีความเกี่ยวข้องในระดับโลก อย่างไรก็ตาม ขนาดไฟล์ Wasm อาจเป็นข้อพิจารณา โดยเฉพาะสำหรับผู้ใช้ที่มีแบนด์วิดท์จำกัด ควรปรับขนาดโมดูล Wasm ของคุณให้เหมาะสม และพิจารณาใช้เทคนิคต่างๆ เช่น code splitting หากแอปพลิเคชันของคุณมีฟังก์ชัน Wasm หลายอย่าง

รูปแบบที่ 5: Worker Pools สำหรับการประมวลผลแบบขนาน

สำหรับงานที่ต้องใช้ CPU อย่างแท้จริงและสามารถแบ่งออกเป็นงานย่อยๆ ที่เป็นอิสระต่อกันได้จำนวนมาก กลุ่มของเวิร์กเกอร์ (worker pool) สามารถให้ประสิทธิภาพที่เหนือกว่าผ่านการประมวลผลแบบขนาน

workerPool.js (โมดูลเวิร์กเกอร์):


// workerPool.js

// จำลองงานที่ใช้เวลา
function performComplexCalculation(input) {
  let result = 0;
  for (let i = 0; i < 1e7; i++) {
    result += Math.sin(input * i) * Math.cos(input / i);
  }
  return result;
}

self.onmessage = function(event) {
  const { taskInput, taskId } = event.data;
  console.log(`Worker ${self.name || ''} processing task ${taskId}`);
  try {
    const result = performComplexCalculation(taskInput);
    self.postMessage({ status: 'success', result: result, taskId: taskId });
  } catch (error) {
    self.postMessage({ status: 'error', error: error.message, taskId: taskId });
  }
};

console.log('สมาชิก Worker pool เริ่มต้นทำงานแล้ว');

main.js (ตัวจัดการ):


// main.js

const MAX_WORKERS = navigator.hardwareConcurrency || 4; // ใช้คอร์ที่มีอยู่, ค่าเริ่มต้นคือ 4
let workers = [];
let taskQueue = [];
let availableWorkers = [];

function initializeWorkerPool() {
  for (let i = 0; i < MAX_WORKERS; i++) {
    const worker = new Worker('./workerPool.js', { type: 'module' });
    worker.name = `Worker-${i}`;
    worker.isBusy = false;

    worker.onmessage = function(event) {
      console.log(`Message from ${worker.name}:`, event.data);
      if (event.data.status === 'success' || event.data.status === 'error') {
        // งานเสร็จสิ้น, ทำเครื่องหมายว่าเวิร์กเกอร์พร้อมใช้งาน
        worker.isBusy = false;
        availableWorkers.push(worker);
        // ประมวลผลงานถัดไปถ้ามี
        processNextTask();
      }
    };

    worker.onerror = function(error) {
      console.error(`Error in ${worker.name}:`, error);
      worker.isBusy = false;
      availableWorkers.push(worker);
      processNextTask(); // พยายามกู้คืน
    };

    workers.push(worker);
    availableWorkers.push(worker);
  }
  console.log(`Worker pool initialized with ${MAX_WORKERS} workers.`);
}

function addTask(taskInput) {
  taskQueue.push({ input: taskInput, id: Date.now() + Math.random() });
  processNextTask();
}

function processNextTask() {
  if (taskQueue.length === 0 || availableWorkers.length === 0) {
    return;
  }

  const worker = availableWorkers.shift();
  const task = taskQueue.shift();

  worker.isBusy = true;
  console.log(`Assigning task ${task.id} to ${worker.name}`);
  worker.postMessage({ taskInput: task.input, taskId: task.id });
}

// การทำงานหลัก
if (window.Worker) {
  initializeWorkerPool();

  // เพิ่มงานลงใน pool
  for (let i = 0; i < 20; i++) {
    addTask(i * 0.1);
  }

} else {
  console.log('Web Workers are not supported.');
}

ข้อควรพิจารณาในระดับโลก: จำนวนคอร์ CPU ที่มีอยู่ (`navigator.hardwareConcurrency`) อาจแตกต่างกันอย่างมากในอุปกรณ์ต่างๆ ทั่วโลก กลยุทธ์ worker pool ของคุณควรเป็นแบบไดนามิก ในขณะที่การใช้ `navigator.hardwareConcurrency` เป็นจุดเริ่มต้นที่ดี ควรพิจารณาการประมวลผลฝั่งเซิร์ฟเวอร์สำหรับงานที่หนักและใช้เวลานานมาก ซึ่งข้อจำกัดฝั่งไคลเอ็นต์อาจยังคงเป็นคอขวดสำหรับผู้ใช้บางราย

แนวทางปฏิบัติที่ดีที่สุดสำหรับการใช้งาน Module Worker ในระดับโลก

เมื่อสร้างสำหรับผู้ชมทั่วโลก แนวทางปฏิบัติที่ดีที่สุดหลายประการมีความสำคัญอย่างยิ่ง:

บทสรุป

JavaScript Module Workers เป็นความก้าวหน้าที่สำคัญในการเปิดใช้งานการประมวลผลเบื้องหลังที่มีประสิทธิภาพและเป็นโมดูลในเบราว์เซอร์ ด้วยการนำรูปแบบต่างๆ มาใช้ เช่น คิวงาน, การย้ายไลบรารีไปประมวลผล, การซิงโครไนซ์แบบเรียลไทม์ และการผสานรวม WebAssembly นักพัฒนาสามารถสร้างเว็บแอปพลิเคชันที่มีประสิทธิภาพสูงและตอบสนองได้ดี ซึ่งรองรับผู้ชมทั่วโลกที่หลากหลาย

การเรียนรู้รูปแบบเหล่านี้จะช่วยให้คุณสามารถจัดการกับงานที่ต้องใช้การคำนวณสูงได้อย่างมีประสิทธิภาพ สร้างความมั่นใจในประสบการณ์ผู้ใช้ที่ราบรื่นและน่าดึงดูดใจ ในขณะที่เว็บแอปพลิเคชันมีความซับซ้อนมากขึ้นและความคาดหวังของผู้ใช้ในด้านความเร็วและการโต้ตอบยังคงเพิ่มสูงขึ้น การใช้ประโยชน์จากพลังของ Module Workers ไม่ใช่สิ่งฟุ่มเฟือยอีกต่อไป แต่เป็นความจำเป็นสำหรับการสร้างผลิตภัณฑ์ดิจิทัลระดับโลก

เริ่มทดลองกับรูปแบบเหล่านี้วันนี้เพื่อปลดล็อกศักยภาพสูงสุดของการประมวลผลเบื้องหลังในแอปพลิเคชัน JavaScript ของคุณ

JavaScript Module Workers: การเรียนรู้รูปแบบการประมวลผลเบื้องหลังสำหรับภูมิทัศน์ดิจิทัลระดับโลก | MLOG