การจัดการงานที่ใช้เวลานานในแอป React ด้วย Web Workers
เผยแพร่แล้ว: 2022-03-10เวลาตอบสนองเป็นเรื่องใหญ่เมื่อพูดถึงเว็บแอปพลิเคชัน ผู้ใช้ต้องการการตอบสนองในทันที ไม่ว่าแอปของคุณจะทำอะไรก็ตาม ไม่ว่าจะเป็นการแสดงเพียงชื่อบุคคลหรือตัวเลขสั้นๆ ผู้ใช้เว็บแอปต้องการให้แอปของคุณตอบสนองต่อคำสั่งของตนทุกครั้ง บางครั้งอาจทำได้ยากเนื่องจาก JavaScript เป็นแบบเธรดเดียว แต่ในบทความนี้ เราจะเรียนรู้วิธีที่เราสามารถใช้ประโยชน์จาก Web Worker API เพื่อมอบประสบการณ์ที่ดียิ่งขึ้น
ในการเขียนบทความนี้ ผมได้ตั้งสมมติฐานดังนี้
- เพื่อให้สามารถปฏิบัติตามได้ อย่างน้อยคุณควรมีความคุ้นเคยกับ JavaScript และ API ของเอกสาร
- คุณควรมีความรู้เกี่ยวกับการทำงานของ React เพื่อให้คุณสามารถเริ่มโครงการ React ใหม่ได้สำเร็จโดยใช้ Create React App
หากคุณต้องการข้อมูลเชิงลึกเพิ่มเติมในหัวข้อนี้ เราได้รวมลิงก์จำนวนหนึ่งไว้ในส่วน "แหล่งข้อมูลเพิ่มเติม" เพื่อช่วยให้คุณตามทัน
ก่อนอื่น มาเริ่มใช้งาน Web Workers กันก่อน
Web Worker คืออะไร?
เพื่อให้เข้าใจ Web Workers และปัญหาที่พวกเขาตั้งใจจะแก้ไข จำเป็นต้องทำความเข้าใจว่าโค้ด JavaScript ถูกรันบนรันไทม์อย่างไร ระหว่างรันไทม์ โค้ด JavaScript จะถูกดำเนินการตามลำดับและในลักษณะผลัดกันเลี้ยว เมื่อโค้ดสิ้นสุดลง โค้ดถัดไปในบรรทัดจะเริ่มทำงาน เป็นต้น ในเชิงเทคนิค เรากล่าวว่า JavaScript เป็นแบบเธรดเดียว ลักษณะการทำงานนี้บ่งบอกว่าเมื่อโค้ดบางส่วนเริ่มทำงาน โค้ดทั้งหมดที่ตามมาจะต้องรอให้โค้ดนั้นดำเนินการเสร็จสิ้น ดังนั้น โค้ดทุกบรรทัด "บล็อก" การดำเนินการของทุกอย่างที่ตามมา ดังนั้นจึงควรให้โค้ดทุกชิ้นเสร็จสิ้นโดยเร็วที่สุด หากโค้ดบางส่วนใช้เวลานานเกินไปกว่าจะเสร็จสิ้น โปรแกรมของเราก็ดูเหมือนจะหยุดทำงาน บนเบราว์เซอร์ หน้านี้ปรากฏเป็นหน้าที่ค้างและไม่ตอบสนอง ในบางกรณีที่รุนแรง แท็บจะหยุดโดยสิ้นเชิง
ลองนึกภาพการขับรถเลนเดียว หากคนขับคนใดที่อยู่ข้างหน้าหยุดเคลื่อนที่ด้วยเหตุผลใดก็ตาม แสดงว่าคุณมีรถติด ด้วยโปรแกรมเช่น Java การจราจรสามารถดำเนินต่อไปในช่องทางอื่น ดังนั้น Java จึงถูกกล่าวว่าเป็นแบบมัลติเธรด Web Workers เป็นความพยายามที่จะนำ พฤติกรรมแบบมัลติเธรด มาสู่ JavaScript
ภาพหน้าจอด้านล่างแสดงให้เห็นว่า Web Worker API ได้รับการสนับสนุนโดยเบราว์เซอร์จำนวนมาก ดังนั้นคุณควรรู้สึกมั่นใจในการใช้งาน
Web Workers ทำงานในเธรดพื้นหลังโดยไม่รบกวน UI และสื่อสารกับโค้ดที่สร้างโดยใช้ตัวจัดการเหตุการณ์
คำจำกัดความที่ยอดเยี่ยมของ Web Worker มาจาก MDN:
“คนงานคืออ็อบเจกต์ที่สร้างขึ้นโดยใช้คอนสตรัคเตอร์ (เช่นWorker()
ที่เรียกใช้ไฟล์ JavaScript ที่มีชื่อ — ไฟล์นี้มีรหัสที่จะรันในเธรดของผู้ปฏิบัติงาน ผู้ปฏิบัติงานทำงานในบริบทส่วนกลางอื่นที่แตกต่างจากwindow
ปัจจุบัน ดังนั้น โดยใช้ทางลัดwindow
เพื่อรับขอบเขตปัจจุบัน (แทนที่จะเป็นself
ภายในWorker
จะส่งคืนข้อผิดพลาด”
ผู้ปฏิบัติงานถูกสร้างขึ้นโดยใช้ตัวสร้าง Worker
const worker = new Worker('worker-file.js')
เป็นไปได้ที่จะเรียกใช้โค้ดส่วนใหญ่ภายใน Web Worker โดยมีข้อยกเว้นบางประการ ตัวอย่างเช่น คุณไม่สามารถจัดการ DOM จากภายในผู้ปฏิบัติงานได้ ไม่มีการเข้าถึง document
API
ผู้ปฏิบัติงานและเธรดที่วางไข่จะส่งข้อความถึงกันโดยใช้ postMessage()
ในทำนองเดียวกัน พวกเขาตอบสนองต่อข้อความโดยใช้ตัวจัดการเหตุการณ์ onmessage
สิ่งสำคัญคือต้องได้รับความแตกต่างนี้ การส่งข้อความทำได้โดยใช้วิธีการ การรับข้อความกลับต้องใช้ตัวจัดการเหตุการณ์ ข้อความที่ได้รับมีอยู่ในแอตทริบิวต์ data
ของเหตุการณ์ เราจะเห็นตัวอย่างนี้ในหัวข้อถัดไป แต่ขอให้ผมพูดถึงอย่างรวดเร็วว่าประเภทของคนงานที่เราคุยกันนั้นเรียกว่า "คนงานที่ทุ่มเท" ซึ่งหมายความว่าผู้ปฏิบัติงานสามารถเข้าถึงได้จากสคริปต์ที่เรียกใช้เท่านั้น นอกจากนี้ยังสามารถมีผู้ปฏิบัติงานที่สามารถเข้าถึงได้จากหลายสคริปต์ สิ่งเหล่านี้เรียกว่าผู้ปฏิบัติงานที่ใช้ร่วมกันและสร้างขึ้นโดยใช้ตัวสร้าง SharedWorker
ดังที่แสดงด้านล่าง
const sWorker = new SharedWorker('shared-worker-file.js')
หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับพนักงาน โปรดดูบทความ MDN นี้ จุดประสงค์ของบทความนี้คือเพื่อให้คุณเริ่มต้นใช้งาน Web Worker มาเริ่มกันโดยการคำนวณเลขฟีโบนักชีที่ n
การคำนวณเลขฟีโบนักชีที่ N
หมายเหตุ: สำหรับสิ่งนี้และอีกสองส่วนถัดไป ฉันใช้ Live Server บน VSCode เพื่อเรียกใช้แอป คุณสามารถใช้อย่างอื่นได้อย่างแน่นอน
นี่คือส่วนที่คุณรอคอย ในที่สุด เราจะเขียนโค้ดบางส่วนเพื่อดูการทำงานของ Web Workers ดีไม่เร็วนัก เราจะไม่ซาบซึ้งกับงานที่ Web Worker ทำ เว้นแต่เราจะพบปัญหาประเภทที่แก้ไขได้ ในส่วนนี้ เราจะดูตัวอย่างปัญหา และในหัวข้อต่อไปนี้ เราจะมาดูกันว่าพนักงานเว็บช่วยให้เราทำงานได้ดีขึ้นได้อย่างไร
ลองนึกภาพว่าคุณกำลังสร้างเว็บแอปที่อนุญาตให้ผู้ใช้คำนวณเลขฟีโบนักชีที่ n ในกรณีที่คุณยังใหม่ต่อคำว่า 'ตัวเลขฟีโบนักชี' คุณสามารถอ่านเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ที่นี่ แต่โดยสรุป ตัวเลขฟีโบนักชีเป็นลำดับของตัวเลข โดยที่แต่ละตัวเลขเป็นผลรวมของตัวเลขสองตัวก่อนหน้า
ทางคณิตศาสตร์จะแสดงเป็น:
ดังนั้นตัวเลขสองสามตัวแรกของลำดับคือ:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 ...
ในบางแหล่ง ลำดับเริ่มต้นที่ F 0 = 0
ซึ่งในกรณีนี้ สูตรด้านล่างใช้สำหรับ n > 1
:
ในบทความนี้ เราจะเริ่มกันที่ F 1 = 1 สิ่งหนึ่งที่เราสามารถเห็นได้ทันทีจากสูตรก็คือตัวเลขนั้นเป็นไปตามรูปแบบการเรียกซ้ำ งานที่ทำอยู่ตอนนี้คือการเขียนฟังก์ชันแบบเรียกซ้ำเพื่อคำนวณเลขฟีโบนักชีที่ n (FN)
หลังจากพยายามไม่กี่ครั้ง ฉันเชื่อว่าคุณสามารถสร้างฟังก์ชันด้านล่างได้อย่างง่ายดาย
const fib = n => { if (n < 2) { return n // or 1 } else { return fib(n - 1) + fib(n - 2) } }
ฟังก์ชั่นเป็นเรื่องง่าย ถ้า n น้อยกว่า 2 ให้คืนค่า n (หรือ 1) มิฉะนั้น ให้ส่งคืนผลรวมของ n-1
และ n-2
FN ด้วยฟังก์ชันลูกศรและตัวดำเนินการ ternary เราสามารถสร้าง one-liner ได้
const fib = n => (n < 2 ? n : fib(n-1) + fib(n-2))
ฟังก์ชันนี้มีความซับซ้อนของเวลาเป็น 0(2 n )
นี่หมายความว่าเมื่อค่าของ n เพิ่มขึ้น เวลาที่ใช้ในการคำนวณผลรวมจะเพิ่มขึ้นแบบทวีคูณ สิ่งนี้ทำให้เป็นงานที่ใช้เวลานานมากซึ่งอาจรบกวน UI ของเราสำหรับค่า n จำนวนมาก ลองดูสิ่งนี้ในการดำเนินการ
หมายเหตุ : นี่ไม่ใช่วิธีที่ดีที่สุดในการแก้ปัญหานี้โดยเฉพาะ ฉันเลือกใช้วิธีนี้เพื่อจุดประสงค์ของบทความนี้
ในการเริ่มต้น ให้สร้างโฟลเดอร์ใหม่และตั้งชื่อตามที่คุณต้องการ ตอนนี้ภายในโฟลเดอร์นั้นสร้างโฟลเดอร์ src/
นอกจากนี้ ให้สร้างไฟล์ index.html
ในโฟลเดอร์รูท ภายในโฟลเดอร์ src/
ให้สร้างไฟล์ชื่อ index.js
เปิด index.html
และเพิ่มโค้ด HTML ต่อไปนี้
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="styles.css"> </head> <body> <div class="heading-container"> <h1>Computing the nth Fibonnaci number</h1> </div> <div class="body-container"> <p id='error' class="error"></p> <div class="input-div"> <input id='number-input' class="number-input" type='number' placeholder="Enter a number" /> <button id='submit-btn' class="btn-submit">Calculate</button> </div> <div id='results-container' class="results"></div> </div> <script src="/src/index.js"></script> </body> </html>
ส่วนนี้ง่ายมาก อันดับแรกเรามีหัวข้อ จากนั้นเราก็มีคอนเทนเนอร์ที่มีอินพุตและปุ่ม ผู้ใช้จะป้อนตัวเลขแล้วคลิก "คำนวณ" นอกจากนี้เรายังมีคอนเทนเนอร์สำหรับเก็บผลการคำนวณ สุดท้าย เรารวมไฟล์ src/index.js
ไว้ในแท็ก script
คุณสามารถลบลิงก์สไตล์ชีตได้ แต่ถ้าคุณไม่มีเวลา ฉันได้กำหนด CSS บางอย่างที่คุณสามารถใช้ได้ เพียงสร้างไฟล์ styles.css
ที่โฟลเดอร์รูท และเพิ่มสไตล์ด้านล่าง:
body { margin: 0; padding: 0; box-sizing: border-box; } .body-container, .heading-container { padding: 0 20px; } .heading-container { padding: 20px; color: white; background: #7a84dd; } .heading-container > h1 { margin: 0; } .body-container { width: 50% } .input-div { margin-top: 15px; margin-bottom: 15px; display: flex; align-items: center; } .results { width: 50vw; } .results>p { font-size: 24px; } .result-div { padding: 5px 10px; border-radius: 5px; margin: 10px 0; background-color: #e09bb7; } .result-div p { margin: 5px; } span.bold { font-weight: bold; } input { font-size: 25px; } p.error { color: red; } .number-input { padding: 7.5px 10px; } .btn-submit { padding: 10px; border-radius: 5px; border: none; background: #07f; font-size: 24px; color: white; cursor: pointer; margin: 0 10px; }
ตอนนี้เปิด src/index.js
ขึ้นมา ค่อยๆ พัฒนามัน เพิ่มรหัสด้านล่าง
const fib = (n) => (n < 2 ? n : fib(n - 1) + fib(n - 2)); const ordinal_suffix = (num) => { // 1st, 2nd, 3rd, 4th, etc. const j = num % 10; const k = num % 100; switch (true) { case j === 1 && k !== 11: return num + "st"; case j === 2 && k !== 12: return num + "nd"; case j === 3 && k !== 13: return num + "rd"; default: return num + "th"; } }; const textCont = (n, fibNum, time) => { const nth = ordinal_suffix(n); return ` <p id='timer'>Time: <span class='bold'>${time} ms</span></p> <p><span class="bold" id='nth'>${nth}</span> fibonnaci number: <span class="bold" id='sum'>${fibNum}</span></p> `; };
ที่นี่เรามีสามฟังก์ชั่น อันแรกคือฟังก์ชันที่เราเห็นก่อนหน้านี้สำหรับการคำนวณ FN ที่ n ฟังก์ชันที่สองเป็นเพียงฟังก์ชันยูทิลิตี้เพื่อแนบคำต่อท้ายที่เหมาะสมกับจำนวนเต็ม ฟังก์ชันที่สามรับอาร์กิวเมนต์และส่งออกมาร์กอัปซึ่งเราจะแทรกใน DOM ในภายหลัง อาร์กิวเมนต์แรกคือจำนวนที่มีการคำนวณ FN อาร์กิวเมนต์ที่สองคือ FN ที่คำนวณ อาร์กิวเมนต์สุดท้ายคือเวลาที่ใช้ในการคำนวณ
ยังอยู่ใน src/index.js
เพิ่มโค้ดด้านล่างใต้โค้ดก่อนหน้า
const errPar = document.getElementById("error"); const btn = document.getElementById("submit-btn"); const input = document.getElementById("number-input"); const resultsContainer = document.getElementById("results-container"); btn.addEventListener("click", (e) => { errPar.textContent = ''; const num = window.Number(input.value); if (num < 2) { errPar.textContent = "Please enter a number greater than 2"; return; } const startTime = new Date().getTime(); const sum = fib(num); const time = new Date().getTime() - startTime; const resultDiv = document.createElement("div"); resultDiv.innerHTML = textCont(num, sum, time); resultDiv.className = "result-div"; resultsContainer.appendChild(resultDiv); });
ขั้นแรก เราใช้ document
API เพื่อรับโหนด DOM
ในไฟล์ HTML ของเรา เราได้รับการอ้างอิงถึงย่อหน้าที่เราจะแสดงข้อความแสดงข้อผิดพลาด อินพุต; ปุ่มคำนวณและคอนเทนเนอร์ที่เราจะแสดงผล
ต่อไป เราแนบตัวจัดการเหตุการณ์ "คลิก" กับปุ่ม เมื่อมีการคลิกปุ่ม เราจะนำสิ่งที่อยู่ภายในองค์ประกอบอินพุตและแปลงเป็นตัวเลข หากเราได้อะไรที่น้อยกว่า 2 เราจะแสดงข้อความแสดงข้อผิดพลาดและส่งคืน ถ้าเราได้จำนวนที่มากกว่า 2 เราก็ทำต่อไป ขั้นแรก เราบันทึกเวลาปัจจุบัน หลังจากนั้นเราคำนวณ FN เมื่อเสร็จสิ้น เราจะได้ความแตกต่างของเวลาที่แสดงถึงระยะเวลาในการคำนวณ ในส่วนที่เหลือของโค้ด เราสร้าง div
ใหม่ จากนั้นเราตั้งค่า HTML ภายในให้เป็นผลลัพธ์ของฟังก์ชัน textCont()
ที่เรากำหนดไว้ก่อนหน้านี้ สุดท้าย เราเพิ่มคลาสให้กับคลาสนั้น (สำหรับการจัดสไตล์) และผนวกเข้ากับคอนเทนเนอร์ผลลัพธ์ ผลของสิ่งนี้คือการคำนวณแต่ละครั้งจะปรากฏใน div
ที่แยกจากกันด้านล่างของค่าก่อนหน้า
เราจะเห็นได้ว่าเมื่อตัวเลขเพิ่มขึ้น เวลาในการคำนวณก็เพิ่มขึ้นด้วย (แบบทวีคูณ) ตัวอย่างเช่น จาก 30 เป็น 35 เรามีเวลาในการคำนวณเพิ่มขึ้นจาก 13ms เป็น 130ms เรายังถือว่าการดำเนินการเหล่านั้น "รวดเร็ว" ได้ ที่ 40 เราเห็นเวลาในการคำนวณมากกว่า 1 วินาที ในเครื่องของฉัน นี่คือจุดที่ฉันเริ่มสังเกตเห็นว่าหน้าไม่ตอบสนอง ณ จุดนี้ ฉันไม่สามารถโต้ตอบกับเพจได้อีกต่อไปในขณะที่กำลังคำนวณอยู่ ฉันไม่สามารถมุ่งความสนใจไปที่ข้อมูลเข้าหรือทำอย่างอื่นได้
จำเมื่อเราพูดถึง JavaScript ที่เป็นเธรดเดียว? เธรดนั้นถูก "บล็อก" โดยการคำนวณที่ใช้เวลานาน ดังนั้นทุกอย่างต้อง "รอ" เพื่อให้เสร็จสิ้น อาจเริ่มต้นที่ค่าที่ต่ำกว่าหรือสูงกว่าในเครื่องของคุณ แต่คุณจะต้องไปถึงจุดนั้น สังเกตว่าใช้เวลาเกือบ 10 วินาทีในการคำนวณของ 44 หากมีสิ่งอื่นที่ต้องทำบนเว็บแอปของคุณ ผู้ใช้ต้องรอให้ Fib(44) ทำงานเสร็จก่อนจึงจะดำเนินการต่อได้ แต่ถ้าคุณปรับใช้ผู้ปฏิบัติงานเว็บเพื่อจัดการกับการคำนวณนั้น ผู้ใช้ของคุณสามารถดำเนินการอย่างอื่นได้ในขณะที่ทำงานนั้น
ตอนนี้เรามาดูกันว่าพนักงานเว็บช่วยเราแก้ปัญหานี้ได้อย่างไร
ตัวอย่างการทำงานของ Web Worker
ในส่วนนี้ เราจะมอบหมายงานในการคำนวณ FN ที่ n ให้กับพนักงานเว็บ สิ่งนี้จะช่วยเพิ่มพื้นที่เธรดหลักและทำให้ UI ของเราตอบสนองในขณะที่การคำนวณกำลังดำเนินอยู่
การเริ่มต้นใช้งานเว็บนั้นง่ายอย่างน่าประหลาดใจ เรามาดูกันว่าเป็นอย่างไร สร้างไฟล์ใหม่ src/fib-worker.js
และป้อนรหัสต่อไปนี้
const fib = (n) => (n < 2 ? n : fib(n - 1) + fib(n - 2)); onmessage = (e) => { const { num } = e.data; const startTime = new Date().getTime(); const fibNum = fib(num); postMessage({ fibNum, time: new Date().getTime() - startTime, }); };
ขอให้สังเกตว่าเราได้ย้ายฟังก์ชันที่คำนวณเลขฟีโบนักชีที่ n fib
ในไฟล์นี้ ไฟล์นี้จะถูกเรียกใช้โดยพนักงานเว็บของเรา
เรียกคืนในส่วน Web worker คืออะไร เรากล่าวถึงผู้ปฏิบัติงานเว็บและผู้ปกครองของพวกเขาสื่อสารโดยใช้ตัวจัดการเหตุการณ์ onmessage
และ postMessage()
ที่นี่เราใช้ตัวจัดการเหตุการณ์ onmessage
เพื่อฟังข้อความจากสคริปต์หลัก เมื่อเราได้รับข้อความ เราจะทำลายตัวเลขจากแอตทริบิวต์ data ของเหตุการณ์ ต่อไป เราได้รับเวลาปัจจุบันและเริ่มการคำนวณ เมื่อผลลัพธ์พร้อมแล้ว เราจะใช้ postMessage()
เพื่อโพสต์ผลลัพธ์กลับไปยังสคริปต์หลัก
เปิด src/index.js
ขึ้นมาทำการเปลี่ยนแปลงบางอย่าง
... const worker = new window.Worker("src/fib-worker.js"); btn.addEventListener("click", (e) => { errPar.textContent = ""; const num = window.Number(input.value); if (num < 2) { errPar.textContent = "Please enter a number greater than 2"; return; } worker.postMessage({ num }); worker.onerror = (err) => err; worker.onmessage = (e) => { const { time, fibNum } = e.data; const resultDiv = document.createElement("div"); resultDiv.innerHTML = textCont(num, fibNum, time); resultDiv.className = "result-div"; resultsContainer.appendChild(resultDiv); }; });
สิ่งแรกที่ต้องทำคือสร้าง Web Worker โดยใช้ตัวสร้าง Worker
จากนั้นในฟังเหตุการณ์ของปุ่ม เราจะส่งหมายเลขไปยังพนักงานโดยใช้ worker.postMessage({ num })
หลังจากนั้นเราตั้งค่าฟังก์ชั่นเพื่อฟังข้อผิดพลาดในตัวงาน ที่นี่เราเพียงแค่ส่งคืนข้อผิดพลาด คุณสามารถทำสิ่งต่างๆ ได้มากขึ้นหากต้องการ เช่น แสดงใน DOM ต่อไป เราจะฟังข้อความจากคนงาน เมื่อเราได้รับข้อความ เราจะทำลาย time
และ fibNum
และดำเนินการตามขั้นตอนเพื่อแสดงใน DOM
โปรดทราบว่าภายใน web worker เหตุการณ์ onmessage
นั้นมีอยู่ในขอบเขตของผู้ปฏิบัติงาน ดังนั้นเราจึงสามารถเขียนเป็น self.onmessage
และ self.postMessage()
แต่ในสคริปต์หลัก เราต้องแนบสิ่งเหล่านี้กับตัวงานเอง
ในภาพหน้าจอด้านล่าง คุณจะเห็นไฟล์ Web Worker ในแท็บแหล่งที่มาของ Chrome Dev Tools สิ่งที่คุณควรสังเกตคือ UI จะตอบสนองไม่ว่าคุณจะป้อนหมายเลขใด พฤติกรรมนี้เป็นความมหัศจรรย์ของคนงานเว็บ
เรามีความคืบหน้าอย่างมากกับเว็บแอปของเรา แต่มีอย่างอื่นที่เราสามารถทำได้เพื่อให้ดีขึ้น การใช้งานปัจจุบันของเราใช้พนักงานคนเดียวในการจัดการทุกการคำนวณ หากมีข้อความใหม่เข้ามาในขณะที่กำลังทำงานอยู่ ข้อความเก่าจะถูกแทนที่ เพื่อหลีกเลี่ยงปัญหานี้ เราสามารถสร้างพนักงานใหม่สำหรับแต่ละการโทรเพื่อคำนวณ FN เรามาดูวิธีการทำกันในหัวข้อถัดไป
การทำงานกับ Web Workers หลายคน
ขณะนี้ เรากำลังดำเนินการทุกคำขอกับพนักงานเพียงคนเดียว ดังนั้นคำขอที่เข้ามาจะแทนที่คำขอก่อนหน้าที่ยังไม่เสร็จสิ้น สิ่งที่เราต้องการในตอนนี้คือการเปลี่ยนแปลงเล็กน้อยเพื่อวางไข่พนักงานเว็บใหม่สำหรับทุกคำขอ เราจะฆ่าคนงานคนนี้เมื่อเสร็จแล้ว
เปิด src/index.js
และย้ายบรรทัดที่สร้างผู้ปฏิบัติงานเว็บภายในตัวจัดการเหตุการณ์การคลิกของปุ่ม ตอนนี้ตัวจัดการเหตุการณ์ควรมีลักษณะดังนี้
btn.addEventListener("click", (e) => { errPar.textContent = ""; const num = window.Number(input.value); if (num < 2) { errPar.textContent = "Please enter a number greater than 2"; return; } const worker = new window.Worker("src/fib-worker.js"); // this line has moved inside the event handler worker.postMessage({ num }); worker.onerror = (err) => err; worker.onmessage = (e) => { const { time, fibNum } = e.data; const resultDiv = document.createElement("div"); resultDiv.innerHTML = textCont(num, fibNum, time); resultDiv.className = "result-div"; resultsContainer.appendChild(resultDiv); worker.terminate() // this line terminates the worker }; });
เราทำการเปลี่ยนแปลงสองครั้ง
- เราย้ายบรรทัดนี้
const worker = new window.Worker("src/fib-worker.js")
ไว้ในตัวจัดการเหตุการณ์การคลิกของปุ่ม - เราได้เพิ่มบรรทัดนี้
worker.terminate()
เพื่อละทิ้งผู้ปฏิบัติงานเมื่อเราทำเสร็จแล้ว
ดังนั้นทุกครั้งที่คลิกปุ่ม เราสร้างพนักงานใหม่เพื่อจัดการกับการคำนวณ ดังนั้นเราจึงสามารถเปลี่ยนแปลงอินพุตได้ และผลลัพธ์แต่ละรายการจะเข้าสู่หน้าจอเมื่อการคำนวณเสร็จสิ้น ในภาพหน้าจอด้านล่าง คุณจะเห็นว่าค่า 20 และ 30 ปรากฏก่อนค่า 45 แต่ฉันเริ่ม 45 ก่อน เมื่อฟังก์ชันส่งคืนสำหรับ 20 และ 30 ผลลัพธ์จะถูกโพสต์ และผู้ปฏิบัติงานยุติการทำงาน เมื่อทุกอย่างเสร็จสิ้น เราไม่ควรมีคนทำงานในแท็บแหล่งที่มา
เราสามารถจบบทความนี้ได้ที่นี่ แต่ถ้านี่เป็นแอปที่ตอบสนอง เราจะนำคนทำงานบนเว็บเข้ามาได้อย่างไร นั่นคือจุดเน้นของส่วนถัดไป
พนักงานเว็บใน React
ในการเริ่มต้น ให้สร้างแอปตอบโต้ใหม่โดยใช้ CRA คัดลอกไฟล์ fib-worker.js
ลงในโฟลเดอร์ public/
ของแอป react ของคุณ การวางไฟล์ไว้ที่นี่เกิดจากการที่แอป React เป็นแอปแบบหน้าเดียว นั่นเป็นสิ่งเดียวที่เฉพาะกับการใช้ผู้ปฏิบัติงานในแอปพลิเคชันการโต้ตอบ ทุกสิ่งที่ตามมาจากที่นี่เป็น React ที่บริสุทธิ์
ในโฟลเดอร์ src/
ให้สร้างไฟล์ helpers.js
และส่งออก ordinal_suffix()
จากมัน
// src/helpers.js export const ordinal_suffix = (num) => { // 1st, 2nd, 3rd, 4th, etc. const j = num % 10; const k = num % 100; switch (true) { case j === 1 && k !== 11: return num + "st"; case j === 2 && k !== 12: return num + "nd"; case j === 3 && k !== 13: return num + "rd"; default: return num + "th"; } };
แอปของเราต้องการให้เรารักษาสถานะบางอย่างไว้ ดังนั้นให้สร้างไฟล์อื่น src/reducer.js
แล้ววางลงในตัวลดสถานะ
// src/reducers.js export const reducer = (state = {}, action) => { switch (action.type) { case "SET_ERROR": return { ...state, err: action.err }; case "SET_NUMBER": return { ...state, num: action.num }; case "SET_FIBO": return { ...state, computedFibs: [ ...state.computedFibs, { id: action.id, nth: action.nth, loading: action.loading }, ], }; case "UPDATE_FIBO": { const curr = state.computedFibs.filter((c) => c.id === action.id)[0]; const idx = state.computedFibs.indexOf(curr); curr.loading = false; curr.time = action.time; curr.fibNum = action.fibNum; state.computedFibs[idx] = curr; return { ...state }; } default: return state; } };
มาดูการกระทำแต่ละประเภทกัน
-
SET_ERROR
: ตั้งค่าสถานะข้อผิดพลาดเมื่อทริกเกอร์ -
SET_NUMBER
: ตั้งค่าในกล่องอินพุตของเราให้เป็นสถานะ -
SET_FIBO
: เพิ่มรายการใหม่ในอาร์เรย์ของ FN ที่คำนวณ -
UPDATE_FIBO
: ที่นี่เราค้นหารายการเฉพาะและแทนที่ด้วยวัตถุใหม่ที่มี FN ที่คำนวณแล้วและเวลาในการคำนวณ
เราจะใช้ตัวลดนี้ในไม่ช้า ก่อนหน้านั้น มาสร้างส่วนประกอบที่จะแสดง FN ที่คำนวณได้ สร้างไฟล์ใหม่ src/Results.js
และวางโค้ดด้านล่าง
// src/Results.js import React from "react"; export const Results = (props) => { const { results } = props; return ( <div className="results-container"> {results.map((fb) => { const { id, nth, time, fibNum, loading } = fb; return ( <div key={id} className="result-div"> {loading ? ( <p> Calculating the{" "} <span className="bold"> {nth} </span>{" "} Fibonacci number... </p> ) : ( <> <p> Time: <span className="bold">{time} ms</span> </p> <p> <span className="bold"> {nth} </span>{" "} fibonnaci number:{" "} <span className="bold"> {fibNum} </span> </p> </> )} </div> ); })} </div> ); };
ด้วยการเปลี่ยนแปลงนี้ เราจะเริ่มกระบวนการแปลงไฟล์ index.html ก่อนหน้าเป็น jsx ไฟล์นี้มีความรับผิดชอบเดียว: นำอาร์เรย์ของอ็อบเจ็กต์ที่เป็นตัวแทนของ FN ที่คำนวณแล้วแสดง ความแตกต่างเพียงอย่างเดียวจากสิ่งที่เรามีก่อนหน้านี้คือการแนะนำ สถานะการโหลด ตอนนี้เมื่อการคำนวณกำลังทำงาน เราจะแสดงสถานะการโหลดเพื่อแจ้งให้ผู้ใช้ทราบว่ามีบางอย่างเกิดขึ้น
มาใส่ในส่วนสุดท้ายโดยอัปเดตโค้ดภายใน src/App.js
รหัสค่อนข้างยาว ดังนั้นเราจะทำในสองขั้นตอน มาเพิ่มบล็อคแรกของโค้ดกัน
import React from "react"; import "./App.css"; import { ordinal_suffix } from "./helpers"; import { reducer } from './reducer' import { Results } from "./Results"; function App() { const [info, dispatch] = React.useReducer(reducer, { err: "", num: "", computedFibs: [], }); const runWorker = (num, id) => { dispatch({ type: "SET_ERROR", err: "" }); const worker = new window.Worker('./fib-worker.js') worker.postMessage({ num }); worker.onerror = (err) => err; worker.onmessage = (e) => { const { time, fibNum } = e.data; dispatch({ type: "UPDATE_FIBO", id, time, fibNum, }); worker.terminate(); }; }; return ( <div> <div className="heading-container"> <h1>Computing the nth Fibonnaci number</h1> </div> <div className="body-container"> <p className="error"> {info.err} </p> // ... next block of code goes here ... // <Results results={info.computedFibs} /> </div> </div> ); } export default App;
เรานำเข้ามาตามปกติ จากนั้นเราสร้างอินสแตนซ์ของฟังก์ชันสถานะและตัวอัปเดตด้วยตะขอ useReducer จากนั้นเรากำหนดฟังก์ชัน runWorker()
ที่ใช้ตัวเลขและ ID และตั้งค่าเกี่ยวกับการเรียกพนักงานเว็บเพื่อคำนวณ FN สำหรับหมายเลขนั้น
โปรดทราบว่าในการสร้างผู้ปฏิบัติงาน เราส่งเส้นทางสัมพัทธ์ไปยังตัวสร้างผู้ปฏิบัติงาน ขณะรันไทม์ โค้ด React ของเราจะแนบมากับไฟล์ public/index.html
จึงสามารถค้นหาไฟล์ fib-worker.js
ในไดเร็กทอรีเดียวกัน เมื่อการคำนวณเสร็จสิ้น (เรียกโดย worker.onmessage
) การดำเนินการ UPDATE_FIBO
จะถูกส่งออกไป และผู้ปฏิบัติงานจะถูกยกเลิกหลังจากนั้น สิ่งที่เรามีตอนนี้ก็ไม่ต่างไปจากเดิมมากนัก
ในบล็อกส่งคืนของคอมโพเนนต์นี้ เราแสดง HTML แบบเดียวกับที่เคยมีมาก่อน นอกจากนี้เรายังส่งอาร์เรย์ตัวเลขที่คำนวณไปยังองค์ประกอบ <Results />
สำหรับการแสดงผล
มาเพิ่มบล็อกสุดท้ายของโค้ดในคำสั่ง return
<div className="input-div"> <input type="number" value={info.num} className="number-input" placeholder="Enter a number" onChange={(e) => dispatch({ type: "SET_NUMBER", num: window.Number(e.target.value), }) } /> <button className="btn-submit" onClick={() => { if (info.num < 2) { dispatch({ type: "SET_ERROR", err: "Please enter a number greater than 2", }); return; } const id = info.computedFibs.length; dispatch({ type: "SET_FIBO", id, loading: true, nth: ordinal_suffix(info.num), }); runWorker(info.num, id); }} > Calculate </button> </div>
เราตั้งค่าตัวจัดการ onChange
บนอินพุตเพื่ออัปเดตตัวแปรสถานะ info.num
บนปุ่ม เรากำหนดตัวจัดการเหตุการณ์ onClick
เมื่อมีการคลิกปุ่ม เราจะตรวจสอบว่าตัวเลขนั้นมากกว่า 2 หรือไม่ โปรดสังเกตว่าก่อนที่จะเรียก runWorker()
อันดับแรก เราจะดำเนินการดำเนินการเพื่อเพิ่มรายการลงในอาร์เรย์ของ FN ที่คำนวณแล้ว รายการนี้จะได้รับการอัปเดตเมื่อผู้ปฏิบัติงานทำงานเสร็จ ด้วยวิธีนี้ ทุกรายการจะคงตำแหน่งของตนในรายการ ไม่เหมือนที่เรามีมาก่อน
สุดท้าย ให้คัดลอกเนื้อหาของ styles.css
จากเดิม และแทนที่เนื้อหาของ App.css
ตอนนี้เรามีทุกอย่างพร้อมแล้ว ตอนนี้เริ่มต้นเซิร์ฟเวอร์ตอบโต้ของคุณและเล่นกับตัวเลขบางตัว จดสถานะการโหลด ซึ่งเป็นการปรับปรุง UX นอกจากนี้ โปรดทราบว่า UI ยังคงตอบสนองแม้ว่าคุณจะป้อนตัวเลขสูงถึง 1,000 แล้วคลิก "คำนวณ"
สังเกตสถานะการโหลดและผู้ปฏิบัติงานที่ใช้งานอยู่ เมื่อคำนวณค่าที่ 46 ผู้ปฏิบัติงานจะถูกฆ่าและสถานะการโหลดจะถูกแทนที่ด้วยผลลัพธ์สุดท้าย
- ซอร์สโค้ดสำหรับแอป React นี้มีอยู่ใน Github และมีแอปที่โฮสต์บน vercel
บทสรุป
วุ้ย ห่างหายกันไปนาน เรามาปิดท้ายกัน ฉันสนับสนุนให้คุณดูรายการ MDN สำหรับผู้ปฏิบัติงานเว็บ (ดูรายการทรัพยากรด้านล่าง) เพื่อเรียนรู้วิธีอื่นๆ ในการใช้คนงานบนเว็บ
ในบทความนี้ เราได้เรียนรู้ว่าพนักงานเว็บคืออะไรและปัญหาประเภทใดที่พวกเขาตั้งใจจะแก้ไข เรายังได้เห็นวิธีการติดตั้งใช้งานโดยใช้ JavaScript แบบธรรมดา ในที่สุด เราก็ได้เห็นวิธีการใช้โปรแกรมทำงานบนเว็บในแอปพลิเคชัน React
เราขอแนะนำให้คุณใช้ประโยชน์จาก API ที่ยอดเยี่ยมนี้เพื่อมอบประสบการณ์ที่ดียิ่งขึ้นให้กับผู้ใช้ของคุณ
แหล่งข้อมูลเพิ่มเติม
-
Console.time()
, เอกสารเว็บ MDN - {JSON} ตัวยึดตำแหน่ง เว็บไซต์อย่างเป็นทางการ
- การใช้ Web Workers, MDN web docs
- เลขฟีโบนักชี Wikipedia
- โอเปอเรเตอร์แบบมีเงื่อนไข (ternary), MDN web docs
-
Document
, Web APIs, MDN web docs - เริ่มต้น สร้างแอป React (เอกสาร)
-
Function.prototype.toString()
, เอกสารเว็บ MDN - IIFE, เอกสารเว็บ MDN
-
workerSetup.js
, บทช่วยสอน Fullstack ที่ยอดเยี่ยม, GitHub - “การเขียนโปรแกรมแบบขนานใน JavaScript โดยใช้ Web Workers” Uday Hiwarale, Medium