ตอนนี้การย้ายการยกของหนักไปยังเทรดเบื้องหลังทำได้ง่ายขึ้นด้วยโมดูล JavaScript ใน Web Worker
JavaScript ทำงานแบบเธรดเดียว ซึ่งหมายความว่าจะทำงานได้ครั้งละ 1 รายการเท่านั้น วิธีนี้ใช้ง่ายและทำงานได้ดีในหลายๆ กรณีบนเว็บ แต่อาจกลายเป็นปัญหาเมื่อเราจำเป็นต้องทำงานอย่างหนัก เช่น การประมวลผลข้อมูล การแยกวิเคราะห์ การคำนวณ หรือการวิเคราะห์ เมื่อมีการส่งแอปพลิเคชันที่ซับซ้อนบนเว็บเพิ่มมากขึ้นเรื่อยๆ ความจำเป็นในการประมวลผลแบบมัลติเธรดมากขึ้น
ในแพลตฟอร์มเว็บ ค่าพื้นฐานที่สำคัญสำหรับการจัดชุดข้อความและการทำงานพร้อมกันคือ Web Workers API ผู้ปฏิบัติงานเป็นกระบวนการแอบสแตรกต์ที่ใช้งานง่ายนอกเหนือไปจากชุดข้อความของระบบปฏิบัติการที่แสดง 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
ประวัติ: ผู้ปฏิบัติงานแบบคลาสสิก
ตัวสร้างผู้ปฏิบัติงานจะใช้ URL ของสคริปต์แบบคลาสสิก ซึ่งสัมพันธ์กับ URL ของเอกสาร และจะส่งกลับการอ้างอิงไปยังอินสแตนซ์ของผู้ปฏิบัติงานใหม่ทันที ซึ่งจะแสดงอินเทอร์เฟซการรับส่งข้อความรวมถึงเมธอด terminate()
ที่จะหยุดและทำลายผู้ปฏิบัติงานทันที
const worker = new Worker('worker.js');
ฟังก์ชัน importScripts()
พร้อมใช้งานภายใน Web Worker สำหรับการโหลดโค้ดเพิ่มเติม แต่จะหยุดการดำเนินการของผู้ปฏิบัติงานชั่วคราวเพื่อดึงข้อมูลและประเมินแต่ละสคริปต์ นอกจากนี้ ยังเรียกใช้สคริปต์ในขอบเขตรวมเหมือนแท็ก <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';
}
ด้วยเหตุนี้ ที่ผ่านมาผู้ปฏิบัติงานบนเว็บจึงสร้างผลกระทบอย่างมากต่อสถาปัตยกรรมของแอปพลิเคชัน นักพัฒนาซอฟต์แวร์ต้องสร้างเครื่องมือที่ชาญฉลาดและวิธีแก้ปัญหาชั่วคราวเพื่อให้สามารถใช้ผู้ปฏิบัติงานบนเว็บได้โดยไม่ต้องเลิกใช้แนวทางการพัฒนาสมัยใหม่ ตัวอย่างเช่น Bundler อย่างเว็บแพ็กฝังการใช้งานตัวโหลดโมดูลขนาดเล็กลงในโค้ดที่สร้างขึ้นซึ่งใช้ importScripts()
สำหรับการโหลดโค้ด แต่รวมโมดูลไว้ในฟังก์ชันเพื่อหลีกเลี่ยงการชนของตัวแปรและจำลองการนำเข้าและส่งออกแบบขึ้นต่อกัน
ป้อนผู้ปฏิบัติงานโมดูล
โหมดใหม่สำหรับผู้ปฏิบัติงานบนเว็บที่ใช้ประโยชน์จากการสรีรศาสตร์และประโยชน์ด้านประสิทธิภาพของโมดูล JavaScript ใน Chrome 80 ซึ่งเรียกว่า "ผู้ปฏิบัติงานโมดูล" ตัวสร้าง Worker
ยอมรับตัวเลือก {type:"module"}
ใหม่ ซึ่งจะเปลี่ยนการโหลดสคริปต์และการดำเนินการให้ตรงกับ <script type="module">
const worker = new Worker('worker.js', {
type: 'module'
});
เนื่องจากผู้ปฏิบัติงานโมดูลเป็นโมดูล JavaScript มาตรฐาน จึงสามารถใช้คำสั่งนำเข้าและส่งออก เช่นเดียวกับโมดูล JavaScript ทั้งหมด ทรัพยากร Dependency จะดำเนินการเพียงครั้งเดียวในบริบทที่กำหนด (เทรดหลัก ผู้ปฏิบัติงาน ฯลฯ) และการนำเข้าทั้งหมดในอนาคตจะอ้างอิงอินสแตนซ์โมดูลที่เรียกใช้แล้ว นอกจากนี้ เบราว์เซอร์ยังเพิ่มประสิทธิภาพการโหลดและเรียกใช้โมดูล JavaScript ด้วย ทรัพยากร Dependency ของโมดูลโหลดได้ก่อนที่จะเรียกใช้โมดูล ซึ่งช่วยให้โหลดต้นไม้โมดูลทั้งโมดูลพร้อมกันได้ การโหลดโมดูลยังแคชโค้ดที่แยกวิเคราะห์แล้ว ซึ่งหมายความว่าโมดูลที่ใช้ในเทรดหลักและในผู้ปฏิบัติงานต้องได้รับการแยกวิเคราะห์เพียงครั้งเดียวเท่านั้น
การย้ายไปยังโมดูล JavaScript ยังทำให้สามารถใช้การนำเข้าแบบไดนามิกสำหรับโค้ดการโหลดแบบ Lazy Loading ได้โดยไม่ต้องบล็อกการดำเนินการของผู้ปฏิบัติงาน การนำเข้าแบบไดนามิกนั้นชัดเจนกว่าการใช้ importScripts()
เพื่อโหลดทรัพยากร Dependency อย่างมาก เนื่องจากมีการส่งคืนการส่งออกของโมดูลที่นำเข้าแทนการอาศัยตัวแปรร่วม
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()
แบบเก่าจะไม่พร้อมใช้งานภายในเครื่องมือทำงานของโมดูลเพื่อประสิทธิภาพที่ยอดเยี่ยม การเปลี่ยนให้ผู้ปฏิบัติงานใช้โมดูล JavaScript หมายความว่าโค้ดทั้งหมดจะโหลดในโหมดเข้มงวด การเปลี่ยนแปลงที่เห็นได้ชัดอีกอย่างคือ ค่า this
ในขอบเขตระดับบนสุดของโมดูล JavaScript คือ undefined
ขณะที่ในผู้ปฏิบัติงานแบบคลาสสิก ค่าจะเป็นขอบเขตทั่วโลกของผู้ปฏิบัติงาน โชคดีที่มี self
ทั่วโลกที่อ้างอิงถึงขอบเขตระดับโลกมาโดยตลอด ฟีเจอร์นี้พร้อมใช้งานสำหรับผู้ปฏิบัติงานทุกประเภท รวมถึง Service Worker และใน DOM
โหลดผู้ปฏิบัติงานล่วงหน้าด้วย modulepreload
การปรับปรุงประสิทธิภาพที่สำคัญอย่างหนึ่งที่มาพร้อมกับผู้ปฏิบัติงานโมดูลก็คือความสามารถในการโหลดผู้ปฏิบัติงานล่วงหน้าและทรัพยากร Dependency ได้ เมื่อใช้ผู้ปฏิบัติงานโมดูล ระบบจะโหลดสคริปต์และเรียกใช้เป็นโมดูล 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 ล่วงหน้านั้นมีจำกัดและไม่จำเป็นต้องเชื่อถือได้ ผู้ปฏิบัติงานแบบคลาสสิกมีประเภททรัพยากร "ผู้ปฏิบัติงาน" สำหรับการโหลดล่วงหน้า แต่ไม่มีเบราว์เซอร์ที่ใช้ <link rel="preload" as="worker">
ดังนั้น เทคนิคหลักที่ใช้ได้สำหรับการโหลดผู้ปฏิบัติงานบนเว็บล่วงหน้าก็คือการใช้ <link rel="prefetch">
ซึ่งอาศัยแคช HTTP ทั้งหมด เมื่อใช้ร่วมกับส่วนหัวการแคชที่ถูกต้อง วิธีนี้จะช่วยให้หลีกเลี่ยงการสร้างอินสแตนซ์สำหรับผู้ปฏิบัติงานที่ต้องรอดาวน์โหลดสคริปต์ของผู้ปฏิบัติงานได้ แต่สิ่งที่ต่างจาก modulepreload
คือเทคนิคนี้ไม่รองรับการโหลดทรัพยากร Dependency หรือการแยกวิเคราะห์ล่วงหน้า
แล้วผู้ปฏิบัติงานที่แชร์เป็นอย่างไร
มีการอัปเดตผู้ปฏิบัติงานที่แชร์ให้รองรับโมดูล 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 การลงทะเบียนโปรแกรมทำงานของบริการต้องเปรียบเทียบสคริปต์ที่นำเข้ากับเวอร์ชันที่แคชไว้ก่อนหน้าเมื่อตัดสินใจว่าจะทริกเกอร์การอัปเดตหรือไม่ และจะต้องดำเนินการสำหรับโมดูล JavaScript เมื่อใช้สำหรับโปรแกรมทำงานของบริการ นอกจากนี้ โปรแกรมทำงานของบริการต้องข้ามแคชสำหรับสคริปต์ได้ในบางกรณีเมื่อตรวจหาการอัปเดต
แหล่งข้อมูลเพิ่มเติมและอ่านเพิ่มเติม
- สถานะฟีเจอร์ ความเห็นพ้องของเบราว์เซอร์ และการกำหนดมาตรฐาน
- การเพิ่มข้อกำหนดของผู้ปฏิบัติงานโมดูลเดิม
- โมดูล JavaScript สำหรับผู้ปฏิบัติงานที่แชร์
- โมดูล JavaScript สำหรับ Service Worker: สถานะการใช้งาน Chrome