เทรดเว็บด้วยผู้ปฏิบัติงานโมดูล

ตอนนี้การย้ายการยกของหนักไปยังเทรดเบื้องหลังทำได้ง่ายขึ้นด้วยโมดูล JavaScript ใน Web Worker

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

บนแพลตฟอร์มเว็บ ค่าพื้นฐานที่สำคัญสำหรับการจัดชุดข้อความและการทำงานพร้อมกันคือ เว็บ 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

ประวัติ: ผู้ปฏิบัติงานแบบคลาสสิก

ตัวสร้างของผู้ปฏิบัติงานใช้ Script 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 เช่น Webpack จะฝังการใช้งานตัวโหลดโมดูลขนาดเล็กลงในโค้ดที่สร้างขึ้นซึ่งใช้ importScripts() สำหรับการโหลดโค้ด แต่จะรวมโมดูลในฟังก์ชันเพื่อหลีกเลี่ยงการขัดแย้งของตัวแปรและจำลอง การนำเข้าและส่งออกทรัพยากร Dependency

ป้อนผู้ปฏิบัติงานโมดูล

โหมดใหม่สำหรับผู้ปฏิบัติงานบนเว็บที่มาพร้อมหลักการยศาสตร์และประโยชน์ด้านประสิทธิภาพของ JavaScript โมดูลจัดส่งใน Chrome 80 ซึ่งเรียกว่า "ผู้ปฏิบัติงานโมดูล" ตัวสร้าง Worker ยอมรับตัวเลือก {type:"module"} ใหม่ ซึ่งจะเปลี่ยนการโหลดสคริปต์และ ให้ตรงกับ <script type="module">

const worker = new Worker('worker.js', {
  type: 'module'
});

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

การย้ายไปยังโมดูล JavaScript ยังทำให้สามารถใช้ dynamic นำเข้าสำหรับโค้ดการโหลดแบบ 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 การใช้ควบคู่กับ Headers การสร้างแคชที่ถูกต้องจะช่วยให้ เพื่อหลีกเลี่ยงการสร้างอินสแตนซ์ให้กับผู้ปฏิบัติงานที่ต้องรอดาวน์โหลดสคริปต์ผู้ปฏิบัติงาน แต่สิ่งที่ต่างจาก 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 เมื่อใช้สำหรับ Service Worker นอกจากนี้ โปรแกรมทำงานของบริการต้องข้าม แคช สำหรับสคริปต์ในบางกรณีเมื่อ กำลังตรวจหาการอัปเดต

แหล่งข้อมูลเพิ่มเติมและอ่านเพิ่มเติม