ใช้ Web Worker เพื่อเรียกใช้ JavaScript ออกจากเทรดหลักของเบราว์เซอร์

สถาปัตยกรรมแบบนอกชุดข้อความหลักช่วยปรับปรุงความน่าเชื่อถือและประสบการณ์ของผู้ใช้ของแอปได้อย่างมาก

ในช่วง 20 ปีที่ผ่านมา เว็บได้พัฒนาไปอย่างมากจากเอกสารแบบคงที่ที่มีรูปแบบและรูปภาพไม่กี่อย่างไปเป็นแอปพลิเคชันแบบไดนามิกที่ซับซ้อน อย่างไรก็ตาม สิ่งหนึ่งที่ยังคงไม่เปลี่ยนแปลงโดยทั่วไปก็คือ เรามีเพียง 1 เทรดต่อแท็บเบราว์เซอร์ (โดยมีข้อยกเว้นบางประการ) ในการแสดงผลเว็บไซต์และเรียกใช้ JavaScript

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

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

ทำไมต้องใช้ Web Worker

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

ในกรณีที่กังวลเกี่ยวกับ Core Web Vitals การดำเนินการเทรดหลักอาจเป็นประโยชน์ โดยเฉพาะอย่างยิ่ง การลดภาระงานจากเทรดหลักไปยังผู้ปฏิบัติงานบนเว็บจะช่วยลดการช่วงชิงชุดข้อความหลัก ซึ่งจะช่วยปรับปรุงเมตริกการตอบสนองการโต้ตอบกับ Next Paint (INP) ของหน้าเว็บได้ เมื่อเทรดหลักมีขั้นตอนประมวลผลน้อยลง เทรดหลักก็จะตอบสนองต่อการโต้ตอบของผู้ใช้ได้เร็วขึ้น

งานเทรดหลักน้อยลง โดยเฉพาะในช่วงเริ่มต้น ยังมีประโยชน์ที่อาจได้รับจาก Largest Contentful Paint (LCP) ด้วยการลดงานที่ใช้เวลานาน การแสดงผลองค์ประกอบ LCP ต้องใช้เวลาเทรดหลัก ทั้งสำหรับการแสดงผลข้อความหรือรูปภาพ ซึ่งมีองค์ประกอบ LCP ที่พบบ่อย และการลดการทำงานของเทรดหลักโดยรวม ก็ช่วยให้มั่นใจได้ว่าองค์ประกอบ LCP ของหน้าเว็บมีแนวโน้มที่จะถูกบล็อกน้อยลงจากงานที่มีราคาสูงซึ่งผู้ปฏิบัติงานเว็บจัดการแทนได้

การจัดชุดข้อความด้วยผู้ปฏิบัติงานบนเว็บ

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

ใน JavaScript เราสามารถรับฟังก์ชันการทำงานที่คล้ายกันจากผู้ปฏิบัติงานบนเว็บมาตั้งแต่ปี 2007 และมีการรองรับในเบราว์เซอร์หลักๆ ทั้งหมดตั้งแต่ปี 2012 ผู้ปฏิบัติงานบนเว็บจะทำงานพร้อมกับเทรดหลัก แต่จะแชร์ตัวแปรไม่ได้ ต่างจากการแยกชุดข้อความของระบบปฏิบัติการ

หากต้องการสร้าง Web Worker ให้ส่งไฟล์ไปยังเครื่องมือสร้างผู้ปฏิบัติงาน ซึ่งจะเรียกใช้ไฟล์นั้นในเทรดแยกต่างหาก:

const worker = new Worker("./worker.js");

สื่อสารกับ Web Worker โดยการส่งข้อความโดยใช้ postMessage API ส่งค่าข้อความเป็นพารามิเตอร์ในการเรียกใช้ postMessage แล้วเพิ่ม Listener เหตุการณ์ข้อความให้กับผู้ปฏิบัติงานโดยทำดังนี้

main.js

const worker = new Worker('./worker.js');
worker.postMessage([40, 2]);

worker.js

addEventListener('message', event => {
  const [a, b] = event.data;

  // Do stuff with the message
  // ...
});

หากต้องการส่งข้อความกลับไปยังชุดข้อความหลัก ให้ใช้ API postMessage เดียวกันใน Web Worker และตั้งค่า Listener เหตุการณ์ในชุดข้อความหลัก ดังนี้

main.js

const worker = new Worker('./worker.js');

worker.postMessage([40, 2]);
worker.addEventListener('message', event => {
  console.log(event.data);
});

worker.js

addEventListener('message', event => {
  const [a, b] = event.data;

  // Do stuff with the message
  postMessage(a + b);
});

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

แต่ถ้าเราสามารถขจัดความยุ่งยากในการสื่อสารระหว่างเทรดหลักกับผู้ปฏิบัติงานทำงานบนเว็บได้ โมเดลนี้ก็อาจเหมาะกับกรณีการใช้งานหลายกรณี โชคดีที่เรามีห้องสมุดดนตรีที่ช่วยคุณได้

Comlink เป็นไลบรารีที่มีเป้าหมายเพื่อช่วยให้คุณใช้ผู้ปฏิบัติงานเว็บได้โดยไม่ต้องคิดถึงรายละเอียดของ postMessage Comlink ช่วยให้คุณแชร์ตัวแปรระหว่างผู้ปฏิบัติงานบนเว็บและเทรดหลักได้เช่นเดียวกับภาษาโปรแกรมอื่นๆ ที่รองรับการแยกชุดข้อความ

คุณสามารถตั้งค่า Comlink โดยนำเข้าใน Web Worker และกำหนดชุดฟังก์ชันที่จะแสดงไปยังเทรดหลัก จากนั้นคุณนำเข้า Comlink ในเทรดหลัก รวมผู้ปฏิบัติงาน และเข้าถึงฟังก์ชันที่เปิดเผย ดังนี้

worker.js

import {expose} from 'comlink';

const api = {
  someMethod() {
    // ...
  }
}

expose(api);

main.js

import {wrap} from 'comlink';

const worker = new Worker('./worker.js');
const api = wrap(worker);

ตัวแปร api ในเทรดหลักจะทำงานเหมือนกับใน Web Worker เว้นแต่ว่าทุกฟังก์ชันจะแสดงผลค่าสัญญา แทนที่จะเป็นตัวค่าเอง

คุณควรย้ายรหัสใดไปยัง Web Worker

ผู้ปฏิบัติงานบนเว็บไม่มีสิทธิ์เข้าถึง DOM และ API จำนวนมาก เช่น WebUSB, WebRTC หรือ Web Audio ดังนั้นคุณจึงไม่สามารถวางแอปที่ใช้สิทธิ์ดังกล่าวให้กับผู้ปฏิบัติงานได้ แต่ถึงอย่างนั้น โค้ดเล็กๆ ทุกโค้ดที่ย้ายไปยังผู้ปฏิบัติงานก็จะซื้อช่องว่างในเทรดหลักเพิ่มเติมสำหรับสิ่งที่ต้องต้องมี เช่น การอัปเดตอินเทอร์เฟซผู้ใช้

ปัญหาหนึ่งสำหรับนักพัฒนาเว็บคือเว็บแอปส่วนใหญ่ใช้เฟรมเวิร์ก UI เช่น Vue หรือ React เพื่อจัดการทุกอย่างในแอป ทุกอย่างเป็นองค์ประกอบของเฟรมเวิร์ก และโดยธรรมชาติแล้วก็เชื่อมโยงกับ DOM ดูเหมือนว่าจะทำให้ย้ายข้อมูลไปยังสถาปัตยกรรม OMT ได้ยาก

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

PROXX: กรณีศึกษาของ OMT

ทีม Google Chrome พัฒนา PROXX ให้เป็นโคลน Minesweeper ที่เป็นไปตามข้อกำหนดของ Progressive Web App ซึ่งรวมถึงการทำงานแบบออฟไลน์และให้ผู้ใช้ได้รับประสบการณ์ที่น่าดึงดูด น่าเสียดายที่เกมเวอร์ชันแรกๆ ทำได้ไม่ดีในอุปกรณ์ที่จำกัดอย่างฟีเจอร์โฟน ซึ่งทำให้ทีมพบว่าเทรดหลักทำให้เกิดจุดคอขวด

ทีมตัดสินใจใช้ผู้ปฏิบัติงานบนเว็บเพื่อแยกสถานะภาพของเกมออกจากตรรกะ ดังนี้

  • เทรดหลักจะจัดการการแสดงผลของภาพเคลื่อนไหวและการเปลี่ยนภาพ
  • ผู้ปฏิบัติงานเว็บมีหน้าที่จัดการตรรกะของเกมซึ่งเป็นการคำนวณเพียงอย่างเดียว

OMT มีผลกระทบที่น่าสนใจต่อประสิทธิภาพของฟีเจอร์โฟนของ PROXX ในเวอร์ชันที่ไม่ใช่ OMT UI จะค้างเป็นเวลา 6 วินาทีหลังจากที่ผู้ใช้โต้ตอบ โดยไม่มีความคิดเห็นใดๆ และผู้ใช้ต้องรอจนครบ 6 วินาทีก่อน จึงจะทำสิ่งอื่นได้

เวลาตอบสนองของ UI ใน PROXX เวอร์ชันไม่ใช่ OMT

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

เวลาตอบสนองของ UI ใน PROXX เวอร์ชัน OMT

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

ผลกระทบของสถาปัตยกรรม OMT

ตามตัวอย่าง PROXX นี้ OMT ทำให้แอปทำงานได้อย่างเสถียรบนอุปกรณ์ที่หลากหลาย แต่ไม่ได้ทำให้แอปทำงานเร็วขึ้น

  • คุณเพียงแค่ย้ายงานจากเทรดหลัก ไม่ได้ลดงาน
  • ค่าใช้จ่ายเพิ่มเติมในการสื่อสารระหว่างผู้ปฏิบัติงานทำงานของเว็บ และเทรดหลักบางครั้งอาจ ทำให้สิ่งต่างๆ ช้าลงเล็กน้อย

พิจารณาข้อดีข้อเสีย

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

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

หมายเหตุเกี่ยวกับเครื่องมือ

ผู้ปฏิบัติงานบนเว็บยังไม่ใช่โปรแกรมยอดนิยม ดังนั้นเครื่องมือโมดูลส่วนใหญ่ เช่น Webpack และ Rollup จึงไม่รองรับเครื่องมือเหล่านี้ตั้งแต่แรก (แต่ก็มี Parcel อยู่ในนั้น) โชคดีที่มีปลั๊กอินสำหรับทำให้ผู้ปฏิบัติงานบนเว็บทำงานด้วย Webpack และ Rollup ได้ ดังนี้

สรุป

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

นอกจากนี้ OMT มีประโยชน์รองดังนี้

  • ซึ่งจะย้ายต้นทุนการเรียกใช้ JavaScript ไปยังเทรดแยกต่างหาก
  • ช่วยเปลี่ยนแปลงต้นทุนสำหรับการแยกวิเคราะห์ ซึ่งหมายความว่า UI อาจเริ่มทำงานได้เร็วขึ้น ซึ่งอาจช่วยลด First Contentful Paint หรือแม้แต่เวลาในการตอบสนอง ซึ่งจะช่วยเพิ่ม คะแนนของ Lighthouse

ผู้ปฏิบัติงานที่ทำงานด้านเว็บไม่จำเป็นต้องน่ากลัวเสมอไป เครื่องมืออย่าง Comlink กำลังช่วยขจัดการลงจากพนักงานและทำให้ผู้ปฏิบัติงานเป็นตัวเลือกที่น่าพอใจสำหรับเว็บแอปพลิเคชันที่หลากหลาย

รูปภาพหลักจาก Unsplash โดย James Peacock