ตอนนี้การย้ายงานหนักไปยังเธรดเบื้องหลังทำได้ง่ายขึ้นด้วยโมดูล JavaScript ใน Web Worker
JavaScript เป็นโมเดลแบบเธรดเดียว ซึ่งหมายความว่าจะดําเนินการได้ครั้งละ 1 รายการเท่านั้น ซึ่งใช้งานง่ายและเหมาะกับหลายกรณีบนเว็บ แต่อาจทำให้เกิดปัญหาเมื่อเราต้องทำงานที่ต้องใช้กำลัง เช่น การประมวลผลข้อมูล การแยกวิเคราะห์ การประมวลผล หรือการวิเคราะห์ เนื่องจากมีการนำแอปพลิเคชันที่ซับซ้อนมากขึ้นมาใช้บนเว็บ ความต้องการการประมวลผลแบบหลายเธรดจึงเพิ่มขึ้น
ในแพลตฟอร์มเว็บ องค์ประกอบพื้นฐานหลักสำหรับการแยกชุดข้อความและการทำงานแบบขนานคือ Web Worker API Worker คือการแยกแยะข้อมูลแบบเบาบนเธรดของระบบปฏิบัติการที่แสดง API การส่งข้อความสําหรับการสื่อสารระหว่างเธรด ซึ่งจะมีประโยชน์อย่างยิ่งเมื่อทำการคํานวณที่มีค่าใช้จ่ายสูงหรือดำเนินการกับชุดข้อมูลขนาดใหญ่ ซึ่งจะช่วยให้เธรดหลักทำงานได้อย่างราบรื่นขณะดำเนินการที่มีค่าใช้จ่ายสูงในเธรดเบื้องหลังอย่างน้อย 1 เธรด
ต่อไปนี้คือตัวอย่างทั่วไปของการใช้ผู้ปฏิบัติงาน ซึ่งสคริปต์ผู้ปฏิบัติงานจะคอยฟังข้อความจากเธรดหลักและตอบกลับโดยการส่งข้อความของตนเอง
page.js:
const worker = new Worker('worker.js');
worker.addEventListener('message', e => {
console.log(e.data);
});
worker.postMessage('hello');
worker.js:
addEventListener('message', e => {
if (e.data === 'hello') {
postMessage('world');
}
});
Web Worker API มีให้บริการในเบราว์เซอร์ส่วนใหญ่มานานกว่า 10 ปี แม้ว่าจะหมายความว่าผู้ปฏิบัติงานได้รับการรองรับเบราว์เซอร์ที่ยอดเยี่ยมและได้รับการเพิ่มประสิทธิภาพอย่างดี แต่ก็หมายความว่าผู้ปฏิบัติงานเหล่านี้อยู่ก่อนโมดูล JavaScript มานานแล้ว เนื่องจากไม่มีระบบโมดูลเมื่อออกแบบผู้ปฏิบัติงาน API สำหรับการโหลดโค้ดลงในผู้ปฏิบัติงานและการสร้างสคริปต์จึงยังคงคล้ายกับวิธีการโหลดสคริปต์แบบซิงค์ซึ่งนิยมในปี 2009
ประวัติ: ผู้ใช้แบบคลาสสิก
ตัวสร้าง Worker จะใช้ URL สคริปต์แบบคลาสสิก ซึ่งสัมพันธ์กับ URL ของเอกสาร โดยจะแสดงผลอินสแตนซ์ของเวิร์กเกอร์ใหม่ทันที ซึ่งแสดงอินเทอร์เฟซการรับส่งข้อความและเมธอด terminate()
ที่หยุดและทำลายเวิร์กเกอร์ทันที
const worker = new Worker('worker.js');
ฟังก์ชัน importScripts()
พร้อมใช้งานภายในเวิร์กเกอร์เว็บสำหรับการโหลดโค้ดเพิ่มเติม แต่จะทำให้การดําเนินการของเวิร์กเกอร์หยุดชั่วคราวเพื่อดึงข้อมูลและประเมินสคริปต์แต่ละรายการ นอกจากนี้ ยังเรียกใช้สคริปต์ในขอบเขตส่วนกลางเช่นเดียวกับแท็ก <script>
แบบคลาสสิก ซึ่งหมายความว่าตัวแปรในสคริปต์หนึ่งจะเขียนทับตัวแปรในสคริปต์อื่นได้
worker.js:
importScripts('greet.js');
// ^ could block for seconds
addEventListener('message', e => {
postMessage(sayHello());
});
greet.js:
// global to the whole worker
function sayHello() {
return 'world';
}
ด้วยเหตุนี้ เวิร์กเกอร์เว็บจึงส่งผลต่อสถาปัตยกรรมของแอปพลิเคชันเป็นอย่างมากในอดีต นักพัฒนาซอฟต์แวร์จึงต้องสร้างเครื่องมือและวิธีแก้ปัญหาที่ชาญฉลาดเพื่อให้ใช้ Web Worker ได้โดยไม่ต้องละทิ้งแนวทางการพัฒนาสมัยใหม่ ตัวอย่างเช่น เครื่องมือรวมไฟล์อย่าง webpack จะฝังการใช้งานโปรแกรมโหลดโมดูลขนาดเล็กลงในโค้ดที่สร้างขึ้นซึ่งใช้ importScripts()
ในการโหลดโค้ด แต่รวมโมดูลไว้ในฟังก์ชันเพื่อหลีกเลี่ยงการชนกันของตัวแปรและจําลองการนําเข้าและส่งออกทรัพยากร
ป้อนผู้ปฏิบัติงานของข้อบังคับ
โหมดใหม่สำหรับเว็บเวิร์กเกอร์ที่มาพร้อมประโยชน์ด้านประสิทธิภาพและความสะดวกสบายของโมดูล JavaScript จะพร้อมใช้งานใน Chrome 80 โดยเรียกว่า "โมดูลเวิร์กเกอร์" ตอนนี้ตัวสร้างWorker
ยอมรับตัวเลือก {type:"module"}
ใหม่แล้ว ซึ่งจะเปลี่ยนการโหลดและการดำเนินการสคริปต์ให้ตรงกับ <script type="module">
const worker = new Worker('worker.js', {
type: 'module'
});
เนื่องจากผู้ปฏิบัติงานโมดูลเป็นโมดูล JavaScript มาตรฐาน จึงสามารถใช้คำสั่งนำเข้าและส่งออกได้ เช่นเดียวกับโมดูล JavaScript ทั้งหมด ระบบจะเรียกใช้ทรัพยากร Dependency เพียงครั้งเดียวในบริบทหนึ่งๆ (เธรดหลัก เวิร์กเกอร์ ฯลฯ) และการนําเข้าในอนาคตทั้งหมดจะอ้างอิงอินสแตนซ์โมดูลที่เรียกใช้แล้ว นอกจากนี้ เบราว์เซอร์ยังเพิ่มประสิทธิภาพการโหลดและการเรียกใช้โมดูล JavaScript ด้วย คุณสามารถโหลดทรัพยากร Dependency ของโมดูลก่อนที่จะเรียกใช้โมดูลได้ ซึ่งจะช่วยให้โหลดทั้งโครงสร้างโมดูลได้พร้อมกัน การโหลดโมดูลยังแคชโค้ดที่แยกวิเคราะห์ด้วย ซึ่งหมายความว่าโมดูลที่ใช้ในเธรดหลักและในเวิร์กเกอร์จะต้องแยกวิเคราะห์เพียงครั้งเดียว
การเปลี่ยนไปใช้โมดูล JavaScript ยังช่วยให้ใช้การนําเข้าแบบไดนามิกสําหรับการโหลดโค้ดแบบเลื่อนเวลาโดยไม่มีการบล็อกการดําเนินการของเวิร์กเกอร์ได้อีกด้วย การนําเข้าแบบไดนามิกจะชัดเจนกว่าการใช้ importScripts()
เพื่อโหลดไลบรารี importScripts()
มาก เนื่องจากระบบจะแสดงผลการส่งออกของโมดูลที่นําเข้าแทนที่จะใช้ตัวแปรส่วนกลาง
worker.js:
import { sayHello } from './greet.js';
addEventListener('message', e => {
postMessage(sayHello());
});
greet.js:
import greetings from './data.js';
export function sayHello() {
return greetings.hello;
}
เมธอด importScripts()
แบบเก่าจะใช้ไม่ได้ภายในโมดูล Workers เพื่อให้มั่นใจว่าจะได้ประสิทธิภาพที่ยอดเยี่ยม การเปลี่ยนให้ทํางานโดยใช้โมดูล JavaScript หมายความว่าระบบจะโหลดโค้ดทั้งหมดในโหมดที่เข้มงวด อีกการเปลี่ยนแปลงที่สําคัญคือค่าของ this
ในขอบเขตระดับบนสุดของโมดูล JavaScript คือ undefined
ส่วนในเวิร์กเกอร์แบบคลาสสิก ค่าคือขอบเขตส่วนกลางของเวิร์กเกอร์ แต่โชคดีที่เรามี self
ระดับส่วนกลางที่อ้างอิงถึงขอบเขตส่วนกลางเสมอ ซึ่งพร้อมใช้งานใน Worker ทุกประเภท รวมถึง Service Worker และใน DOM
โหลดผู้ปฏิบัติงานล่วงหน้าด้วย modulepreload
การปรับปรุงประสิทธิภาพที่สําคัญอย่างหนึ่งที่มาพร้อมกับโมดูลเวิร์กเกอร์คือความสามารถในการโหลดเวิร์กเกอร์และทรัพยากรที่เกี่ยวข้องล่วงหน้า เมื่อใช้โมดูลเวิร์กเกอร์ ระบบจะโหลดและสคริปต์จะทำงานเป็นโมดูล JavaScript มาตรฐาน ซึ่งหมายความว่าสามารถโหลดล่วงหน้าและแยกวิเคราะห์ล่วงหน้าได้โดยใช้modulepreload
<!-- preloads worker.js and its dependencies: -->
<link rel="modulepreload" href="worker.js">
<script>
addEventListener('load', () => {
// our worker code is likely already parsed and ready to execute!
const worker = new Worker('worker.js', { type: 'module' });
});
</script>
ทั้งชุดข้อความหลักและผู้ปฏิบัติงานของโมดูลยังใช้โมดูลที่โหลดไว้ล่วงหน้าได้อีกด้วย ซึ่งมีประโยชน์สำหรับโมดูลที่นําเข้าในทั้ง 2 บริบท หรือในกรณีที่ไม่สามารถทราบล่วงหน้าว่าจะนําโมดูลไปใช้ในเธรดหลักหรือในเวิร์กเกอร์
ก่อนหน้านี้ ตัวเลือกสำหรับการโหลดสคริปต์ Web Worker ล่วงหน้ามีจํากัดและอาจไม่น่าเชื่อถือ ทรัพยากรประเภท "worker" ของ <link rel="preload" as="worker">
เวอร์ชันคลาสสิกมีไว้สำหรับการโหลดล่วงหน้า แต่ไม่มีเบราว์เซอร์ใดที่ใช้ <link rel="preload" as="worker">
ด้วยเหตุนี้ เทคนิคหลักที่ใช้โหลดเว็บเวิร์กล่วงหน้าได้คือการใช้ <link rel="prefetch">
ซึ่งอาศัยแคช HTTP ทั้งหมด เมื่อใช้ร่วมกับส่วนหัวการแคชที่ถูกต้อง การดำเนินการนี้จะช่วยหลีกเลี่ยงการสร้างอินสแตนซ์ผู้ปฏิบัติงานที่ต้องรอดาวน์โหลดสคริปต์ผู้ปฏิบัติงาน อย่างไรก็ตาม เทคนิคนี้ไม่รองรับการโหลดพึ่งพาล่วงหน้าหรือการแยกวิเคราะห์ล่วงหน้า ซึ่งต่างจาก modulepreload
ผู้ใช้ที่แชร์จะเป็นอย่างไร
เวิร์กเกอร์ที่แชร์ได้รับการอัปเดตให้รองรับโมดูล JavaScript ใน Chrome เวอร์ชัน 83 เช่นเดียวกับผู้ปฏิบัติงานเฉพาะ การสร้างผู้ปฏิบัติงานที่แชร์ด้วยตัวเลือก {type:"module"}
จะโหลดสคริปต์ผู้ปฏิบัติงานเป็นโมดูลแทนสคริปต์คลาสสิก
const worker = new SharedWorker('/worker.js', {
type: 'module'
});
ก่อนที่จะรองรับโมดูล JavaScript ตัวสร้าง SharedWorker()
ต้องการเพียง URL และอาร์กิวเมนต์ name
(ไม่บังคับ) การดำเนินการนี้จะยังคงใช้งานได้สำหรับการใช้งานเวิร์กเกอร์ที่แชร์แบบคลาสสิก แต่การสร้างเวิร์กเกอร์ที่แชร์ของโมดูลต้องใช้อาร์กิวเมนต์ options
ใหม่ ตัวเลือกที่ใช้ได้จะเหมือนกับตัวเลือกสําหรับผู้ทํางานเฉพาะ รวมถึงตัวเลือก name
ที่แทนที่อาร์กิวเมนต์ name
ก่อนหน้า
แล้ว Service Worker ล่ะ
ข้อกำหนดของ Service Worker ได้รับการอัปเดตแล้วเพื่อรองรับการยอมรับข้อบังคับของ JavaScript เป็นจุดแรกเข้า โดยใช้ตัวเลือก {type:"module"}
เดียวกันกับข้อบังคับของโมดูล แต่การเปลี่ยนแปลงนี้ยังไม่พร้อมใช้งานในเบราว์เซอร์ เมื่อดำเนินการดังกล่าวแล้ว คุณจะสามารถสร้างอินสแตนซ์ Service Worker โดยใช้โมดูล JavaScript โดยใช้โค้ดต่อไปนี้
navigator.serviceWorker.register('/sw.js', {
type: 'module'
});
เมื่อมีการอัปเดตข้อกําหนดแล้ว เบราว์เซอร์ต่างๆ ก็เริ่มใช้ลักษณะการทํางานแบบใหม่ ซึ่งอาจใช้เวลาสักครู่เนื่องจากมีความซับซ้อนเพิ่มเติมบางอย่างที่เกี่ยวข้องกับการนำโมดูล JavaScript ไปใช้กับ Service Worker การลงทะเบียน Service Worker ต้องเปรียบเทียบสคริปต์ที่นําเข้ากับเวอร์ชันที่แคชไว้ก่อนหน้านี้เมื่อพิจารณาว่าจะทริกเกอร์การอัปเดตหรือไม่ และจะต้องติดตั้งใช้งานสําหรับโมดูล JavaScript เมื่อใช้กับ Service Worker นอกจากนี้ ผู้ให้บริการต้องสามารถข้ามแคชสคริปต์ได้ในบางกรณีเมื่อตรวจสอบการอัปเดต
แหล่งข้อมูลเพิ่มเติมและแหล่งอ่านเพิ่มเติม
- สถานะฟีเจอร์ ความเห็นพ้องของเบราว์เซอร์ และมาตรฐาน
- การเพิ่มข้อกำหนดเฉพาะของแรงงานในโมดูลต้นฉบับ
- โมดูล JavaScript สําหรับเวิร์กเกอร์ที่ใช้ร่วมกัน
- โมดูล JavaScript สําหรับ Service Worker: สถานะการใช้งาน Chrome