Contents

Event Loop คืออะไรและจะปรับปรุงประสิทธิภาพของแอปได้อย่างไร

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

JavaScript Event Loop คืออะไร?

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

ลูปเหตุการณ์จะรักษารายการของการดำเนินการที่จะดำเนินการ ซึ่งจะส่งตามลำดับไปยัง Web API ที่เหมาะสมสำหรับการประมวลผล JavaScript ดูแลกระบวนการนี้และจัดการการดำเนินการของแต่ละการดำเนินการตามความซับซ้อน

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

ใช้รหัสนี้ตัวอย่างเช่น:

 function longRunningFunction() {
  // This function does something that takes a long time to execute.
  for (var i = 0; i < 100000; i\+\+) {
    console.log("Hello")
  }
}

function shortRunningFunction(a) {
  return a * 2 ;
}

function main() {
  var startTime = Date.now();
  longRunningFunction();
  
  var endTime = Date.now();

  // Prints the amount of time it took to execute functions
  console.log(shortRunningFunction(2));
  console.log("Time taken: " \+ (endTime-startTime) \+ " milliseconds");
}

main();

รหัสปัจจุบันแนะนำฟังก์ชันชื่อ “longRunningFunction” ฟังก์ชันดังกล่าวออกแบบมาเพื่อดำเนินการที่ซับซ้อนและใช้เวลามาก โดยเฉพาะอย่างยิ่ง มันใช้ลูป"for"ซึ่งวนซ้ำหนึ่งแสนครั้ง ดังนั้น คำสั่ง “console.log(“Hello”)” จะถูกดำเนินการในจำนวนการวนซ้ำที่เท่ากัน ส่งผลให้ข้อความ “Hello” หนึ่งแสนอินสแตนซ์ถูกบันทึกลงในคอนโซล

ประสิทธิภาพของโปรแกรมอาจได้รับผลกระทบจากความเร็วในการประมวลผลของคอมพิวเตอร์ ทำให้การทำงานของ shortRunningFunction() ล่าช้าจนกว่าฟังก์ชันก่อนหน้านี้จะเสร็จสิ้น

ข้อมูลที่ให้มาเปรียบเทียบเวลาดำเนินการสำหรับสองฟังก์ชัน ซึ่งมีดังต่อไปนี้:

/th/images/longfunction_console.jpg

จากนั้น shortRunningFunction() เดียว:

/th/images/shortfunction_console.jpg

ความแตกต่างระหว่างขั้นตอนที่ใช้เวลา 2,351 มิลลิวินาทีจึงจะเสร็จสมบูรณ์กับขั้นตอนที่ใช้เวลาไม่นาน จะเห็นได้อย่างชัดเจนเมื่อพยายามทำให้แอปพลิเคชันมีประสิทธิภาพสูงสุด

Event Loop ช่วยประสิทธิภาพของแอพได้อย่างไร

ลูปเหตุการณ์ครอบคลุมชุดของเฟสและส่วนประกอบที่เชื่อมต่อกัน ซึ่งช่วยอำนวยความสะดวกในการทำงานของระบบโดยรวม

กองโทร

JavaScript call stack มีบทบาทสำคัญในการจัดการฟังก์ชันและการเรียกใช้เหตุการณ์ภายในแอปพลิเคชัน ระหว่างการคอมไพล์ โค้ด JavaScript จะไล่จากบนลงล่าง ในทางตรงกันข้าม เมื่อ Node.js ประมวลผลโค้ด โค้ดจะเริ่มที่ด้านล่างและค่อยๆ เลื่อนขึ้น โดยส่งคำจำกัดความของฟังก์ชันไปยัง call stack ทีละรายการเมื่อพบ

Call Stack ทำหน้าที่รักษาบริบทการดำเนินการและตรวจสอบลำดับการเรียกใช้ฟังก์ชันที่เหมาะสม โดยบรรลุสิ่งนี้ผ่านการปรับใช้เป็นโครงสร้าง Last-In-First-Out (LIFO)

การจัดเรียงของฟังก์ชันภายในโปรแกรมถูกควบคุมโดยสแต็ก โดยที่รายการที่เพิ่มล่าสุดจะอยู่ด้านบนจนกว่าจะถูกลบหรือดำเนินการ ดังนั้น เมื่อเรียกใช้โค้ด JavaScript เฟรมฟังก์ชันสุดท้ายที่พุชไปยัง call stack น่าจะเป็นเฟรมเริ่มต้นที่แตกออกและดำเนินการตามลำดับ ซึ่งจะเป็นการรักษาลำดับการดำเนินการที่ต้องการ

/th/images/callstackdiagram.jpg

การใช้ JavaScript จะส่งผลให้มีการลบทุกเฟรมออกจากสแต็กจนกว่าเนื้อหาจะหมดลง ซึ่งแสดงว่าฟังก์ชันทั้งหมดได้ดำเนินการเสร็จสิ้นแล้ว

Libuv เว็บ API

หัวใจหลักของโปรแกรมอะซิงโครนัสของ JavaScript คือ libuv ไลบรารี libuv เขียนด้วยภาษาโปรแกรม C ซึ่งสามารถโต้ตอบกับ API ระดับต่ำของระบบปฏิบัติการได้ ไลบรารีจะมี API หลายตัวที่อนุญาตให้โค้ด JavaScript ทำงานควบคู่ไปกับโค้ดอื่นๆ API สำหรับสร้างเธรด API สำหรับการสื่อสารระหว่างเธรด และ API สำหรับจัดการการซิงโครไนซ์เธรด

เมื่อใช้ setTimeout ภายในสภาพแวดล้อม Node.js ไลบรารี libuv พื้นฐานจะรับผิดชอบในการดูแลลูปเหตุการณ์และเรียกใช้ฟังก์ชันการเรียกกลับที่กำหนดเมื่อหมดช่วงเวลาที่กำหนดไว้ล่วงหน้า

ในทำนองเดียวกัน เมื่อดำเนินการเครือข่ายแบบอะซิงโครนัส libuv ใช้วิธีการที่ไม่ขัดขวางโดยการจัดการงานดังกล่าวในลักษณะที่ช่วยให้กระบวนการอื่นๆ

คิวการโทรกลับและเหตุการณ์

คิวการโทรกลับและเหตุการณ์ทำหน้าที่เป็นพื้นที่รอสำหรับฟังก์ชันการโทรกลับที่คาดว่าจะดำเนินการ เมื่องานแบบอะซิงโครนัสที่เริ่มต้นโดย libuv เสร็จสิ้น ฟังก์ชันการโทรกลับที่เกี่ยวข้องจะถูกวางในคิวนี้เพื่อรอคำแนะนำเพิ่มเติม

ต่อไปนี้เป็นลำดับ:

การใช้ความสามารถของ libuv ทำให้ JavaScript ออฟโหลดการทำงานแบบอะซิงโครนัสไปยัง libuv เพื่อการจัดการที่มีประสิทธิภาพ ทำให้สามารถดำเนินการต่อไปได้อย่างราบรื่นโดยไม่ชักช้า

เมื่อเสร็จสิ้นการดำเนินการแบบอะซิงโครนัสใน JavaScript ฟังก์ชันการเรียกกลับที่เกี่ยวข้องจะถูกเพิ่มไปยังคิวที่กำหนดสำหรับงานดังกล่าว

การใช้จาวาสคริปต์ช่วยให้สามารถทำงานต่าง ๆ ภายในลำดับที่กำหนดไว้ล่วงหน้าได้ เนื่องจากจาวาสคริปต์ยังคงดำเนินการต่อไปจนกว่าจะเสร็จสิ้นตามลำดับตามลำดับ

เมื่องานทั้งหมดใน call stack ได้รับการประมวลผลแล้ว และไม่มีคำแนะนำเพิ่มเติมในการดำเนินการ JavaScript จะตรวจสอบคิวการเรียกกลับ คิวการเรียกกลับทำหน้าที่เป็นพื้นที่พักสำหรับฟังก์ชันที่ต้องดำเนินการในภายหลังหรือเมื่อตรงตามเงื่อนไขบางประการ เมื่อตรวจสอบคิวนี้ JavaScript สามารถมั่นใจได้ว่าจะจัดการกับงานที่ค้างอยู่ก่อนที่จะยุติการดำเนินการ

หากมีการโทรกลับอยู่ในคิว การเรียกกลับจะถูกส่งไปที่ด้านบนสุดของ call stack และดำเนินการตามนั้น

ด้วยการใช้วิธีการแบบอะซิงโครนัส เธรดหลักจะไม่ถูกกีดขวางในขณะที่ดำเนินการที่ไม่ใช่การบำรุงรักษา ด้วยคิวการเรียกกลับที่รับประกันการดำเนินการตามลำดับเวลาของฟังก์ชันการเรียกกลับที่เกี่ยวข้องเมื่อเสร็จสิ้น

รอบวนรอบเหตุการณ์

/th/images/eventloopdiagram.jpg

ลูปเหตุการณ์ประกอบด้วยคิวเฉพาะที่เรียกว่าคิวไมโครทาสก์ ซึ่งได้รับการออกแบบมาเพื่อรองรับไมโครทาสก์ที่กำหนดให้ดำเนินการทันทีเมื่อเสร็จสิ้นงานที่กำลังดำเนินอยู่ในปัจจุบันภายในสแตกการเรียก งานเหล่านี้จะดำเนินการก่อนที่จะมีการเรนเดอร์หรือการวนซ้ำของเหตุการณ์ในภายหลัง ไมโครทาสก์มีลำดับความสำคัญสูงกว่างานมาตรฐานภายในลูปเหตุการณ์และมีความสำคัญเหนือสิ่งเหล่านั้น

เมื่อใช้ Promises ในการเขียนโปรแกรม วิธีการทั่วไปเกี่ยวข้องกับการสร้างงานย่อย เมื่อแก้ไขหรือปฏิเสธสัญญาแล้ว การโทรกลับตามลำดับ**.then() หรือ.catch()** จะถูกเพิ่มไปยังคิวไมโครทาสก์ สามารถใช้คิวนี้เพื่อจัดการงานที่ต้องให้ความสนใจทันทีหลังการดำเนินการที่กำลังดำเนินอยู่ รวมถึงการอัปเดตส่วนประกอบอินเทอร์เฟซผู้ใช้และการปรับเปลี่ยนสถานะแอปพลิเคชัน

ตัวอย่างตัวอย่างของเว็บแอปพลิเคชันดังกล่าวทำให้ผู้ใช้โต้ตอบกับเว็บผ่านอินเทอร์เฟซผู้ใช้แบบกราฟิก (GUI) ซึ่งผู้ใช้อาจดำเนินการต่างๆ เช่น การคลิกปุ่มเพื่อขอข้อมูลจากเซิร์ฟเวอร์ในลักษณะที่ไม่มีการปิดกั้น กระบวนการนี้เกี่ยวข้องกับระบบที่เรียกใช้งานแบบอะซิงโครนัสเมื่อกดปุ่มแต่ละครั้ง ซึ่งจะดำเนินการดึงข้อมูลจากระยะไกลโดยไม่หยุดการดำเนินการอื่นๆ

การทำงานของลูปเหตุการณ์สำหรับงานเฉพาะนี้สามารถเข้าใจได้ในกรณีที่ไม่มีงานย่อยโดยพิจารณาจากลำดับเหตุการณ์ต่อไปนี้:

⭐ผู้ใช้คลิกปุ่มซ้ำๆ

เมื่อคลิกปุ่ม การดำเนินการดึงข้อมูลแบบไม่ปิดกั้นจะเริ่มขึ้น

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

การเริ่มต้นของลูปเหตุการณ์เกี่ยวข้องกับการจัดการงานภายในคิวงานมาตรฐาน ซึ่งมีลักษณะการทำงานแบบธรรมดาและเป็นธรรมเนียมปฏิบัติ

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

ไมโครทาสก์ทำงานภายใต้กลไกที่แตกต่างกันภายในลูปเหตุการณ์ วิธีการมาตรฐานตามด้วยงานมาโครไม่สามารถใช้ได้กับพวกเขา

เมื่อคลิกปุ่มที่กำหนด การร้องขอข้อมูลจะเริ่มต้นในลักษณะอะซิงโครนัสโดยผู้ใช้ ทำให้การดำเนินการต่างๆ เริ่มขึ้น

เมื่อดึงข้อมูลสำเร็จ เหตุการณ์วนซ้ำจะดำเนินการจัดคิวฟังก์ชันการเรียกกลับที่เกี่ยวข้องภายในคิวไมโครทาสก์เพื่อดำเนินการ

Event Loop ออกแบบมาเพื่อจัดการงานอย่างมีประสิทธิภาพโดยจัดลำดับความสำคัญตามประเภทที่เกี่ยวข้อง เช่น งานส่วนต่อประสานกับผู้ใช้และงานย่อย เมื่องานเสร็จสิ้น เช่น การคลิกปุ่ม เหตุการณ์วนรอบจะดำเนินต่อไปโดยอัตโนมัติเพื่อประมวลผลไมโครทาสก์ที่ค้างอยู่ในคิวโดยไม่ชักช้า

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

นี่คือตัวอย่างโค้ด:

 const fetchData = () => {
  return new Promise(resolve => {
    setTimeout(() => resolve('Data from fetch'), 2000);
  });
};

document.getElementById('fetch-button').addEventListener('click', () => {
  fetchData().then(data => {//This UI update will run before the next rendering cycle
    updateUI(data);
  });
});

เมื่อคลิกปุ่ม “ดึงข้อมูล” ในกรณีนี้ ฟังก์ชัน fetchData() จะถูกเรียกใช้ หลังจากการดึงข้อมูล กระบวนการจะถูกกำหนดให้เป็นงานย่อย หลังจากเสร็จสิ้นภารกิจการรับข้อมูล การอัปเดตส่วนต่อประสานผู้ใช้จะเกิดขึ้นโดยไม่ชักช้า ก่อนหน้ากิจกรรมการเรนเดอร์เพิ่มเติมหรือกิจกรรมวนรอบเหตุการณ์

เพื่อป้องกันเวลาแฝงที่เกิดจากงานที่กำลังดำเนินอยู่อื่นๆ ภายในลูปเหตุการณ์ วิธีการนี้รับประกันว่าผู้ใช้จะได้รับข้อมูลที่เป็นปัจจุบันทันที

การรวมไมโครทาสก์ภายในแอปพลิเคชัน เช่น แอปพลิเคชันนี้ อาจช่วยลดปัญหาอินเทอร์เฟซผู้ใช้ (UI) ที่ติดขัดหรือการขัดจังหวะที่เรียกว่า"jank"ซึ่งส่งผลให้ผู้ใช้โต้ตอบได้อย่างรวดเร็วและราบรื่นยิ่งขึ้น

ผลกระทบของ Event Loop สำหรับการพัฒนาเว็บ

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

Node.js นำเสนอโซลูชันที่ครอบคลุม ครอบคลุมผู้ปฏิบัติงานเว็บที่อำนวยความสะดวกในการทำงานคู่ขนานเพิ่มเติมนอกเหนือจากบริบทการดำเนินการ JavaScript หลัก