event loop คือกลไกของ Node.js สำหรับจัดการการดำเนินการ I/O ที่ไม่บล็อก - แม้ว่า JavaScript จะเป็นแบบเธรดเดียว - โดยการถ่ายการดำเนินการไปยังเคอร์เนลของระบบเมื่อเป็นไปได้
เนื่องจากคอร์ส่วนใหญ่ในปัจจุบันเป็นแบบมัลติเธรด จึงสามารถรองรับการดำเนินการต่างๆ ในเบื้องหลังได้ เมื่อการดำเนินการอย่างใดอย่างหนึ่งเสร็จสิ้น เคอร์เนลจะแจ้งให้ Node.js เพิ่มฟังก์ชันการเรียกกลับที่เหมาะสมลงในคิวการโพลและรอโอกาสในการดำเนินการ เราจะแนะนำโดยละเอียดในบทความนี้
เมื่อเริ่มต้น Node.js มันจะเริ่มต้นลูปเหตุการณ์และประมวลผลสคริปต์อินพุตที่ให้มา (หรือโยนลงใน REPL ซึ่งไม่ได้กล่าวถึงในบทความนี้) มันอาจจะเรียก API แบบอะซิงโครนัสบางตัว ตัวจับเวลากำหนดการ หรือเรียก process.nextTick()
จากนั้นเริ่มประมวลผลลูปเหตุการณ์
แผนภาพด้านล่างแสดงภาพรวมอย่างง่ายของลำดับการดำเนินการของลูปเหตุการณ์
┌─>│ ตัวจับเวลา │ │ │ รอการติดต่อกลับ │ │ │ ไม่ได้ใช้งาน เตรียมตัว │ │ ┌─────────────┴────────────┐ │ เข้ามา: │ │ │ โพล │<─────┤ การเชื่อมต่อ │ │ └─────────────┬─────────────┘ │ ข้อมูล ฯลฯ │ │ │ ตรวจสอบ │ └──┤ ปิดการโทรกลับ │ └─────────────────────────────┘
หมายเหตุ: แต่ละกล่องเรียกว่าระยะของกลไกการวนซ้ำเหตุการณ์
แต่ละขั้นตอนมีคิว FIFO เพื่อดำเนินการโทรกลับ แม้ว่าแต่ละสเตจจะมีความพิเศษ โดยทั่วไปเมื่อลูปเหตุการณ์เข้าสู่สเตจที่กำหนด จะดำเนินการใดๆ ที่เฉพาะเจาะจงสำหรับสเตจนั้น จากนั้นดำเนินการเรียกกลับในคิวของสเตจนั้นจนกว่าคิวจะหมดหรือดำเนินการตามจำนวนการเรียกกลับสูงสุดแล้ว เมื่อคิวหมดหรือถึงขีดจำกัดการโทรกลับ ลูปเหตุการณ์จะย้ายไปยังเฟสถัดไป และต่อๆ ไป
เนื่องจากการดำเนินการใดๆ เหล่านี้อาจกำหนดเวลาการดำเนินการเพิ่มเติมและเหตุการณ์ใหม่ที่จัดคิวโดยเคอร์เนลเพื่อประมวลผลในระหว่างขั้นตอน การโพล กิจกรรมการโพลอาจถูกจัดคิวในขณะที่ประมวลผลเหตุการณ์ในเฟสการโพล ดังนั้น การโทรกลับที่ใช้เวลานานสามารถอนุญาตให้ระยะการโพลทำงานนานกว่าเวลาขีดจำกัดของตัวจับเวลา ดูส่วนตัว จับเวลา และ การสำรวจ สำหรับข้อมูลเพิ่มเติม
หมายเหตุ: มีความแตกต่างเล็กน้อยระหว่างการใช้งาน Windows และ Unix/Linux แต่สิ่งนี้ไม่สำคัญสำหรับวัตถุประสงค์ของการสาธิต ส่วนที่สำคัญที่สุดอยู่ที่นี่ จริงๆ แล้วมีเจ็ดหรือแปดขั้นตอน แต่สิ่งที่เราสนใจคือ Node.js ใช้ขั้นตอนข้างต้นบางส่วนจริงๆ
ตัวจับเวลาsetTimeout()
และ setInterval()
setImmediate()
) ในกรณีอื่น ๆ โหนดจะบล็อกที่นี่เมื่อเหมาะสมsetImmediate()
ดำเนินการที่นี่socket.on('close', ...)
.ระหว่างการเรียกใช้เหตุการณ์แต่ละครั้ง Node.js จะตรวจสอบเพื่อดูว่ากำลังรอ I/O หรือตัวจับเวลาแบบอะซิงโครนัสหรือไม่ และหากไม่เป็นเช่นนั้น จะปิดตัวลงอย่างสมบูรณ์
ตัวจับเวลาจะระบุ ขีดจำกัด ที่สามารถดำเนินการเรียกกลับที่ให้มาได้ แทนที่จะเป็นเวลาที่แน่นอนที่ผู้ใช้ต้องการให้ดำเนินการ หลังจากช่วงเวลาที่กำหนด การเรียกกลับของตัวจับเวลาจะทำงานโดยเร็วที่สุด อย่างไรก็ตาม อาจเกิดความล่าช้าเนื่องจากการกำหนดเวลาระบบปฏิบัติการหรือการเรียกกลับที่ทำงานอยู่อื่นๆ
หมายเหตุ : เฟส การโพล จะควบคุมเมื่อตัวจับเวลาดำเนินการ
ตัวอย่างเช่น สมมติว่าคุณตั้งเวลาให้หมดเวลาหลังจาก 100 มิลลิวินาที จากนั้นสคริปต์ของคุณเริ่มอ่านไฟล์แบบอะซิงโครนัสซึ่งใช้เวลา 95 มิลลิวินาที:
const fs = need('fs'); ฟังก์ชั่น someAsyncOperation (โทรกลับ) { // สมมติว่าใช้เวลา 95 มิลลิวินาทีจึงจะเสร็จสมบูรณ์ fs.readFile('/path/to/file', โทรกลับ); - const timeoutScheduled = Date.now(); setTimeout(() => { const ล่าช้า = Date.now () - กำหนดเวลาหมดเวลา; console.log(`${delay}ms ผ่านไปแล้วตั้งแต่ฉันถูกกำหนดไว้`); }, 100); // ทำบางอย่างAsyncOperation ซึ่งใช้เวลา 95 มิลลิวินาทีจึงจะเสร็จสมบูรณ์ someAsyncOperation(() => { const startCallback = Date.now(); // ทำบางอย่างที่จะใช้เวลา 10ms... ในขณะที่ (Date.now() - startCallback < 10) { //ไม่ทำอะไรเลย - });
เมื่อลูปเหตุการณ์เข้าสู่ขั้นตอน การโพล จะมีคิวว่าง ( fs.readFile()
ยังไม่เสร็จสิ้น) ดังนั้นจะรอจำนวนมิลลิวินาทีที่เหลือจนกว่าจะถึงเกณฑ์ตัวจับเวลาที่เร็วที่สุด เมื่อรอ 95 มิลลิวินาทีเพื่อให้ fs.readFile()
อ่านไฟล์เสร็จสิ้น การเรียกกลับซึ่งใช้เวลา 10 มิลลิวินาทีจึงจะเสร็จสิ้นจะถูกเพิ่มลงในคิว การโพล และดำเนินการ เมื่อการโทรกลับเสร็จสมบูรณ์ จะไม่มีการเรียกกลับในคิวอีกต่อไป ดังนั้นกลไกการวนซ้ำเหตุการณ์จะดูตัวจับเวลาที่ถึงเกณฑ์เร็วที่สุด จากนั้นจะกลับไปที่เฟส ตัวจับเวลา เพื่อดำเนินการโทรกลับของตัวจับเวลา ในตัวอย่างนี้ คุณจะเห็นว่าความล่าช้าทั้งหมดระหว่างตัวจับเวลาที่กำหนดและการเรียกกลับที่กำลังดำเนินการคือ 105 มิลลิวินาที
หมายเหตุ: เพื่อป้องกันไม่ให้เฟส การสำรวจ อดอาหารลูปเหตุการณ์ libuv (ไลบรารี C ที่ใช้ลูปเหตุการณ์ Node.js และพฤติกรรมอะซิงโครนัสทั้งหมดของแพลตฟอร์ม) ก็มีค่าสูงสุดฮาร์ดเช่นกัน ( ขึ้นอยู่กับระบบ)
ระยะนี้ดำเนินการเรียกกลับสำหรับการดำเนินการของระบบบางอย่าง (เช่น ประเภทข้อผิดพลาด TCP) ตัวอย่างเช่น ระบบ *nix บางระบบต้องการรอเพื่อรายงานข้อผิดพลาด หากซ็อกเก็ต TCP ได้รับ ECONNREFUSED
เมื่อพยายามเชื่อมต่อ สิ่งนี้จะถูกจัดคิวเพื่อดำเนินการในระหว่างขั้นตอน การติดต่อกลับที่รอดำเนินการ
เฟส การโพล มีสองฟังก์ชันที่สำคัญ:
การคำนวณระยะเวลาที่ I/O ควรถูกบล็อคและโพล
จากนั้น จัดการเหตุการณ์ในคิว การโพล
เมื่อลูปเหตุการณ์เข้าสู่เฟส การโพล และไม่มีกำหนดเวลาไว้ หนึ่งในสองสิ่งจะเกิดขึ้น:
หากคิว การโพล ไม่ว่างเปล่า
ลูปเหตุการณ์จะวนซ้ำผ่านคิวการโทรกลับและดำเนินการพร้อมกันจนกว่าคิวจะว่างเปล่า หรือถึงขีดจำกัดฮาร์ดที่เกี่ยวข้องกับระบบแล้ว
หากคิว โพล ว่างเปล่า จะมีอีกสองสิ่งเกิดขึ้น:
หากสคริปต์ถูกกำหนดโดย setImmediate()
ลูปเหตุการณ์จะสิ้นสุดขั้นตอน การโพล และดำเนินการขั้นตอน ตรวจสอบ ต่อไปเพื่อรันสคริปต์ที่กำหนดเวลาไว้เหล่านั้น
หากสคริปต์ ไม่ได้ กำหนดเวลาโดย setImmediate()
ลูปเหตุการณ์จะรอให้เพิ่มการโทรกลับลงในคิวแล้วจึงดำเนินการทันที
เมื่อคิว โพล ว่างเปล่า ลูปเหตุการณ์จะตรวจสอบตัวจับเวลาที่ถึงเกณฑ์เวลาแล้ว หากมีตัวจับเวลาอย่างน้อยหนึ่งตัวพร้อม ลูปเหตุการณ์จะวนกลับไปที่เฟสตัวจับเวลาเพื่อดำเนินการเรียกกลับสำหรับตัวจับเวลาเหล่านั้น
เฟสนี้อนุญาตให้ดำเนินการโทรกลับทันทีหลังจากขั้นตอนการโพลเสร็จสิ้น หากเฟสการโพลกลายเป็นไม่ได้ใช้งานและสคริปต์อยู่ในคิวหลังจากใช้ setImmediate()
ลูปเหตุการณ์อาจดำเนินการต่อไปยังเฟส การตรวจสอบ แทนที่จะรอ
setImmediate()
จริงๆ แล้วเป็นตัวจับเวลาพิเศษที่ทำงานในเฟสที่แยกจากกันของลูปเหตุการณ์ โดยจะใช้ libuv API เพื่อกำหนดเวลาการดำเนินการเรียกกลับหลังจากขั้นตอน การโพล เสร็จสิ้น
โดยทั่วไป เมื่อรันโค้ด ลูปเหตุการณ์จะเข้าสู่ขั้นตอนการโพลในที่สุด ซึ่งจะรอการเชื่อมต่อขาเข้า คำขอ ฯลฯ อย่างไรก็ตาม หากมีกำหนดเวลาการโทรกลับโดยใช้ setImmediate()
และระยะการโพลกลายเป็นไม่ได้ใช้งาน การดำเนินการจะสิ้นสุดระยะนี้และดำเนินการต่อในเฟสตรวจสอบ แทนที่จะรอเหตุการณ์การโพลต่อไป
ถ้าซ็อกเก็ตหรือตัวจัดการถูกปิดกะทันหัน (เช่น socket.destroy()
) เหตุการณ์ 'close'
จะถูกปล่อยออกมาในขั้นตอนนี้ มิฉะนั้นมันจะถูกส่งผ่าน process.nextTick()
setImmediate()
และ setTimeout()
มีความคล้ายคลึงกันมาก แต่จะมีลักษณะการทำงานที่แตกต่างกันตามเวลาที่ถูกเรียก
setImmediate()
ได้รับการออกแบบมาเพื่อรันสคริปต์เมื่อขั้นตอน การโพล ปัจจุบันเสร็จสมบูรณ์setTimeout()
รันสคริปต์หลังจากผ่านเกณฑ์ขั้นต่ำ (เป็น ms) แล้วลำดับการดำเนินการตัวจับเวลาจะแตกต่างกันไปขึ้นอยู่กับบริบทที่ถูกเรียก หากทั้งสองถูกเรียกจากภายในโมดูลหลัก ตัวจับเวลาจะถูกผูกไว้กับประสิทธิภาพของกระบวนการ (ซึ่งอาจได้รับผลกระทบจากแอปพลิเคชันอื่นที่ทำงานอยู่บนคอมพิวเตอร์)
ตัวอย่างเช่น หากคุณรันสคริปต์ต่อไปนี้ซึ่งไม่อยู่ในวงจร I/O (เช่น โมดูลหลัก) ลำดับที่ตัวจับเวลาสองตัวถูกดำเนินการจะไม่ถูกกำหนดไว้ เนื่องจากถูกผูกไว้กับประสิทธิภาพของกระบวนการ:
// timeout_vs_immediate.js setTimeout(() => { console.log('หมดเวลา'); }, 0); setImmediate(() => { console.log('ทันที'); - $ โหนด timeout_vs_immediate.js หมดเวลา ทันที $ โหนด timeout_vs_immediate.js ทันที การหมดเวลา
อย่างไรก็ตาม หากคุณใส่ทั้งสองฟังก์ชันนี้ไว้ในลูป I/O และเรียกใช้ฟังก์ชันเหล่านั้น setImmediate จะถูกเรียกก่อนเสมอ:
// timeout_vs_immediate.js const fs = ต้องการ('fs'); fs.readFile(__ชื่อไฟล์, () => { setTimeout(() => { console.log('หมดเวลา'); }, 0); setImmediate(() => { console.log('ทันที'); - - $ โหนด timeout_vs_immediate.js ทันที หมดเวลา $ โหนด timeout_vs_immediate.js ทันทีข้อได้เปรียบหลักของการใช้ setImmediate() สำหรับ
การหมดเวลา
setImmediate()
setTimeout()
ก็คือ หากมีการกำหนด setImmediate()
ในระหว่างรอบ I/O ก็จะถูกดำเนินการก่อนตัวจับเวลาใดๆ ในนั้น ขึ้นอยู่กับจำนวนตัวจับเวลาที่มีไม่เกี่ยวข้องกับ
คุณอาจสังเกตเห็น process.nextTick()
ไม่ได้แสดงในไดอะแกรม แม้ว่าจะเป็นส่วนหนึ่งของ API แบบอะซิงโครนัสก็ตาม เนื่องจากในทางเทคนิคแล้ว process.nextTick()
ไม่ได้เป็นส่วนหนึ่งของลูปเหตุการณ์ แต่จะจัดการ nextTickQueue
หลังจากการดำเนินการปัจจุบันเสร็จสิ้น โดยไม่คำนึงถึงระยะปัจจุบันของลูปเหตุการณ์ การดำเนินการที่นี่ถือเป็นการเปลี่ยนจากตัวประมวลผล C/C++ พื้นฐาน และจัดการโค้ด JavaScript ที่จำเป็นต้องดำเนินการ
เมื่อมองย้อนกลับไปที่ไดอะแกรมของเรา ทุกครั้งที่มีการเรียก process.nextTick()
ในเฟสที่กำหนด การเรียกกลับทั้งหมดที่ส่งไปยัง process.nextTick()
จะได้รับการแก้ไขก่อนที่ลูปเหตุการณ์จะดำเนินต่อไป สิ่งนี้สามารถสร้างสถานการณ์ที่ไม่ดีได้ เนื่องจาก ช่วยให้คุณสามารถ "อดอาหาร" I/O ของคุณผ่านการเรียก process.nextTick()
เพื่อป้องกันไม่ให้ลูปเหตุการณ์ไปถึงขั้นตอน การโพล
เหตุใดจึงมีสิ่งนี้รวมอยู่ใน Node.js ส่วนหนึ่งคือปรัชญาการออกแบบที่ API ควรเป็นแบบอะซิงโครนัสเสมอ แม้ว่าจะไม่จำเป็นต้องเป็นเช่นนั้นก็ตาม ใช้ข้อมูลโค้ดนี้เป็นตัวอย่าง:
function apiCall(arg, callback) { ถ้า (ประเภทของ arg !== 'สตริง') ส่งคืนกระบวนการ nextTick ( โทรกลับ TypeError ใหม่ ('อาร์กิวเมนต์ควรเป็นสตริง') - }
ข้อมูลโค้ดสำหรับการตรวจสอบพารามิเตอร์ หากไม่ถูกต้อง ข้อผิดพลาดจะถูกส่งไปยังฟังก์ชันโทรกลับ API ได้รับการอัปเดตเมื่อเร็วๆ นี้เพื่อให้สามารถส่งผ่านอาร์กิวเมนต์ไปยัง process.nextTick()
ซึ่งจะอนุญาตให้ยอมรับอาร์กิวเมนต์ใดๆ หลังจากตำแหน่งของฟังก์ชันการเรียกกลับ และส่งผ่านข้อโต้แย้งไปยังฟังก์ชันการเรียกกลับเป็นอาร์กิวเมนต์ไปยังฟังก์ชันการเรียกกลับ ดังนั้นคุณจึงไม่มี เพื่อซ้อนฟังก์ชัน
สิ่งที่เรากำลังทำคือส่งข้อผิดพลาดกลับไปยังผู้ใช้ แต่หลังจากโค้ดที่เหลือของผู้ใช้ถูกดำเนินการแล้วเท่านั้น เมื่อใช้ process.nextTick()
เรารับประกันว่า apiCall()
จะดำเนินการฟังก์ชันเรียกกลับทุกครั้งหลังจากโค้ดผู้ใช้ที่เหลือ และก่อนที่จะปล่อยให้ลูปเหตุการณ์ดำเนินต่อไป เพื่อให้บรรลุเป้าหมายนี้ JS call stack จะได้รับอนุญาตให้ผ่อนคลาย จากนั้นจึงดำเนินการ callback ที่ให้ไว้ทันที ซึ่งช่วยให้สามารถเรียกซ้ำไปยัง process.nextTick()
ได้โดยไม่ต้องกด RangeError: 超过V8 的最大调用堆栈大小
หลักการออกแบบนี้อาจนำไปสู่ปัญหาที่อาจเกิดขึ้นได้ ใช้ข้อมูลโค้ดนี้เป็นตัวอย่าง:
ให้แถบ; // สิ่งนี้มีลายเซ็นแบบอะซิงโครนัส แต่โทรกลับพร้อมกัน ฟังก์ชั่น someAsyncApiCall (โทรกลับ) { โทรกลับ (); - // การโทรกลับจะถูกเรียกก่อนที่ `someAsyncApiCall` จะเสร็จสมบูรณ์ someAsyncApiCall(() => { // เนื่องจาก someAsyncApiCall เสร็จสมบูรณ์แล้ว bar จึงไม่ได้รับการกำหนดค่าใดๆ console.log('bar', bar); // ไม่ได้กำหนด - bar = 1;
ผู้ใช้กำหนด someAsyncApiCall()
ว่ามีลายเซ็นแบบอะซิงโครนัส แต่อันที่จริงแล้วมันทำงานพร้อมกัน เมื่อมันถูกเรียก การเรียกกลับที่ให้กับ someAsyncApiCall()
จะถูกเรียกภายในเฟสเดียวกันของลูปเหตุการณ์ เนื่องจาก someAsyncApiCall()
ไม่ได้ทำอะไรแบบอะซิงโครนัสจริงๆ ด้วยเหตุนี้ ฟังก์ชันการโทรกลับจึงพยายามอ้างอิง bar
แต่ตัวแปรอาจยังไม่อยู่ในขอบเขตเนื่องจากสคริปต์ยังทำงานไม่เสร็จ
ด้วยการวางการเรียกกลับใน process.nextTick()
สคริปต์ยังคงมีความสามารถในการทำงานจนเสร็จสิ้น โดยอนุญาตให้ตัวแปร ฟังก์ชัน ฯลฯ ทั้งหมดเริ่มต้นได้ก่อนที่จะเรียกการเรียกกลับ นอกจากนี้ยังมีข้อดีคือไม่ปล่อยให้ลูปเหตุการณ์ดำเนินต่อไป และเหมาะสำหรับการเตือนผู้ใช้เมื่อเกิดข้อผิดพลาดก่อนที่จะปล่อยให้ลูปเหตุการณ์ดำเนินต่อไป นี่คือตัวอย่างก่อนหน้านี้โดยใช้ process.nextTick()
:
la bar; ฟังก์ชั่น someAsyncApiCall (โทรกลับ) { กระบวนการ nextTick (โทรกลับ); - someAsyncApiCall(() => { console.log('บาร์', บาร์); // 1 - bar = 1;
นี่เป็นอีกตัวอย่างที่แท้จริง:
const server = net.createServer(() => {}).listen(8080); server.on('listening', () => {});
เฉพาะเมื่อพอร์ตผ่านไปเท่านั้น พอร์ตจะถูกผูกไว้ทันที ดังนั้นจึงสามารถโทรกลับแบบ 'listening'
ได้ทันที ปัญหาคือว่าการโทรกลับของ .on('listening')
ไม่ได้ถูกตั้งค่า ณ เวลานั้น
เพื่อแก้ไขปัญหานี้ เหตุการณ์ 'listening'
จะถูกจัดคิวไว้ภายใน nextTick()
เพื่อให้สคริปต์ทำงานจนเสร็จสิ้น สิ่งนี้ทำให้ผู้ใช้สามารถตั้งค่าตัวจัดการเหตุการณ์อะไรก็ได้ที่พวกเขาต้องการ
เท่าที่ผู้ใช้กังวล เรามีการเรียกที่คล้ายกันสองครั้ง แต่ชื่อของพวกเขาทำให้เกิดความสับสน
process.nextTick()
จะถูกดำเนินการทันทีในขั้นตอนเดียวกันsetImmediate()
เริ่มทำงานในการวนซ้ำครั้งถัดไปหรือ 'ทำเครื่องหมาย' ของลูปเหตุการณ์โดยพื้นฐานแล้ว ทั้งสองชื่อควรสลับกันเนื่องจาก process.nextTick()
เริ่มทำงานเร็วกว่า setImmediate()
แต่นี่เป็นมรดกจากอดีตจึงไม่น่าจะเปลี่ยนแปลงได้ หากคุณทำการสลับชื่ออย่างไม่ตั้งใจ คุณจะทำลายแพ็คเกจส่วนใหญ่ในเวลา npm มีการเพิ่มโมดูลใหม่มากขึ้นทุกวัน ซึ่งหมายความว่าทุกวันที่เราต้องรอ ความเสียหายที่อาจเกิดขึ้นได้มากขึ้น แม้ว่าชื่อเหล่านี้จะทำให้เกิดความสับสน แต่ชื่อเองก็จะไม่เปลี่ยนแปลง
เราขอแนะนำให้นักพัฒนาใช้ setImmediate()
ในทุกสถานการณ์เพราะง่ายต่อการเข้าใจ
มีเหตุผลสองประการ:
เพื่อให้ผู้ใช้สามารถจัดการกับข้อผิดพลาด ทำความสะอาดทรัพยากรที่ไม่จำเป็น หรือลองส่งคำขออีกครั้งก่อนที่เหตุการณ์จะดำเนินต่อไป
有时有让回调在栈展开后,但在事件循环继续之前运行的必要。
以下是一个符合用户预期的简单示例:
const server = net.createServer(); server.on('การเชื่อมต่อ', (conn) => {}); เซิร์ฟเวอร์ ฟัง (8080); server.on('listening', () => {});
สมมติว่า listen()
ทำงานที่จุดเริ่มต้นของลูปเหตุการณ์ แต่การโทรกลับในการฟังจะอยู่ใน setImmediate()
除非传递过主机名,才会立即绑定到端口。 เพื่อให้ลูปเหตุการณ์ดำเนินต่อไปได้ จะต้องเข้าสู่ขั้นตอน การโพล ซึ่งหมายความว่าอาจเป็นไปได้ว่าได้รับการเชื่อมต่อและกิจกรรมการเชื่อมต่อได้เริ่มทำงานก่อนเหตุการณ์การฟัง
อีกตัวอย่างหนึ่งรันตัวสร้างฟังก์ชันที่สืบทอดมาจาก EventEmitter
และต้องการเรียกตัวสร้าง:
const EventEmitter = need('events'); const util = ต้องการ ('util'); ฟังก์ชั่น MyEmitter() { EventEmitter.call (นี้); this.emit('เหตุการณ์'); - util.สืบทอด (MyEmitter, EventEmitter); const myEmitter = ใหม่ MyEmitter(); myEmitter.on('เหตุการณ์', () => { console.log('มีเหตุการณ์เกิดขึ้น!'); });
คุณไม่สามารถทริกเกอร์เหตุการณ์ได้ทันทีจาก Constructor เนื่องจากสคริปต์ยังไม่ได้ประมวลผลจนถึงจุดที่ผู้ใช้กำหนดฟังก์ชันเรียกกลับให้กับเหตุการณ์ ดังนั้นในตัวสร้างเอง คุณสามารถใช้ process.nextTick()
เพื่อตั้งค่าการโทรกลับเพื่อให้เหตุการณ์ถูกปล่อยออกมาหลังจากที่ตัวสร้างเสร็จสมบูรณ์ ซึ่งเป็นสิ่งที่คาดหวังไว้:
const EventEmitter = need('events'); const util = ต้องการ ('util'); ฟังก์ชั่น MyEmitter() { EventEmitter.call (นี้); // use nextTick to emit the event once a handler is assigned process.nextTick(() => { this.emit('เหตุการณ์'); - - util.สืบทอด (MyEmitter, EventEmitter); const myEmitter = ใหม่ MyEmitter(); myEmitter.on('เหตุการณ์', () => { console.log('มีเหตุการณ์เกิดขึ้น!'); });
ที่มา: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/