ตอนนี้การย้ายการยกของหนักไปยังเทรดเบื้องหลังทำได้ง่ายขึ้นด้วยโมดูล 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';
}
ด้วยเหตุนี้ เวิร์กเกอร์เว็บจึงส่งผลต่อสถาปัตยกรรมของแอปพลิเคชันเป็นอย่างมากในอดีต นักพัฒนาซอฟต์แวร์ต้องสร้างเครื่องมือที่ชาญฉลาดและวิธีแก้ปัญหาชั่วคราวเพื่อให้สามารถใช้ผู้ปฏิบัติงานบนเว็บได้โดยไม่ต้องเลิกใช้แนวทางการพัฒนาสมัยใหม่ ตัวอย่างเช่น เครื่องมือรวมไฟล์อย่าง 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 ล่ะ
ข้อกำหนดของโปรแกรมทำงานของบริการได้รับการอัปเดตเพื่อรองรับการยอมรับโมดูล 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