วิธีเริ่มต้นใช้งาน VUE3.0 อย่างรวดเร็ว: เรียนรู้ว่า
แม้ว่า JavaScript จะเป็นแบบเธรดเดียว แต่ event loop จะใช้เคอร์เนลของระบบมากที่สุดเท่าที่จะเป็นไปได้ ทำให้ Node.js สามารถดำเนินการ I/O แบบไม่บล็อกได้ แม้ว่าเคอร์เนลสมัยใหม่ส่วนใหญ่จะเป็นแบบมัลติเธรด แต่ก็สามารถจัดการงานแบบมัลติเธรดได้ใน พื้นหลัง เมื่องานเสร็จสิ้น เคอร์เนลจะบอก Node.js จากนั้นการโทรกลับที่เหมาะสมจะถูกเพิ่มเข้าไปในลูปเพื่อดำเนินการ บทความนี้จะแนะนำหัวข้อนี้โดยละเอียด
เมื่อ Node.js เริ่มดำเนินการ ลูปเหตุการณ์ จะถูกเตรียมใช้งานก่อน ประมวลผลสคริปต์อินพุตที่ให้มา (หรือใส่ลงใน REPL ซึ่งไม่ได้กล่าวถึงในเอกสารนี้) ซึ่งจะดำเนินการเรียก API แบบอะซิงโครนัส กำหนดเวลาหรือเรียกใช้กระบวนการ nextTick() จากนั้น เริ่มการประมวลผลลูปเหตุการณ์
รูปต่อไปนี้แสดงลำดับการดำเนินการของลูปเหตุการณ์ ภาพรวมแบบง่าย
┌──────────────────────┐ ┌─>│ ตัวจับเวลา │ │ │ รอการติดต่อกลับ │ │ │ ไม่ได้ใช้งาน เตรียมตัว │ │ ┌─────────────┴────────────┐ │ เข้ามา: │ │ │ โพล │<─────┤ การเชื่อมต่อ │ │ └─────────────┬─────────────┘ │ ข้อมูล ฯลฯ │ │ │ ตรวจสอบ │ └──┤ ปิดการโทรกลับ │ └─────────────────────────┘
แต่ละกล่องแสดงถึงขั้นตอนของลูปเหตุการณ์
อย่างไรก็ตาม แต่ละขั้นตอนจะมีการดำเนินการเรียกกลับคิว FIFO แต่ละสเตจจะดำเนินการในลักษณะของตัวเอง โดยทั่วไป เมื่อลูปเหตุการณ์เข้าสู่สเตจ จะดำเนินการใดๆ ในสเตจปัจจุบัน และเริ่มดำเนินการเรียกกลับในคิวของสเตจปัจจุบันจนกว่าคิวจะถูกใช้หรือดำเนินการจนหมด ข้อมูลสูงสุด เมื่อคิวหมดหรือถึงขนาดสูงสุด ลูปเหตุการณ์จะย้ายไปยังเฟสถัดไป
ตัวจับเวลาในแต่ละกระบวนการของลูปเหตุการณ์ โหนด .js ตรวจสอบเพื่อดูว่ากำลังรอ I/O แบบอะซิงโครนัสและตัวจับเวลาหรือไม่ และหากไม่ได้ปิด
จะระบุจุดวิกฤตที่จะดำเนินการโทรกลับ แทนที่จะเป็นเวลาที่ต้องการ เพื่อดำเนินการ ตัวจับเวลาจะดำเนินการโดยเร็วที่สุดหลังจากเวลาที่ผ่านไปที่ระบุ อย่างไรก็ตาม การกำหนดเวลาระบบปฏิบัติการหรือการโทรกลับอื่น ๆ อาจทำให้การดำเนินการล่าช้าได้
ในทางเทคนิคแล้ว ขั้นตอนการสำรวจความคิดเห็นจะกำหนดว่าเมื่อใดที่จะดำเนินการโทรกลับ
ตัวอย่างเช่น คุณตั้งเวลาให้ดำเนินการหลังจาก 100 ms แต่สคริปต์ของคุณจะอ่านไฟล์แบบอะซิงโครนัสและใช้เวลา 95ms
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 ms, fs .readFile() อ่านไฟล์เสร็จแล้ว และจะใช้เวลา 10 มิลลิวินาทีในการเพิ่มไปยังเฟสการสำรวจความคิดเห็นและดำเนินการให้เสร็จสิ้น เมื่อการโทรกลับเสร็จสิ้น จะไม่มีการเรียกกลับในคิวที่จะดำเนินการ และลูปเหตุการณ์จะกลับสู่เฟสตัวจับเวลา เพื่อดำเนินการโทรกลับตัวจับเวลา ในตัวอย่างนี้ คุณจะเห็นว่าตัวจับเวลาล่าช้าเป็นเวลา 105 มิลลิวินาทีก่อนที่จะดำเนินการ
เพื่อป้องกันไม่ให้เฟสโพลบล็อกลูปเหตุการณ์ libuv (ไลบรารีภาษา C ที่ใช้ลูปเหตุการณ์และพฤติกรรมอะซิงโครนัสทั้งหมดบนแพลตฟอร์ม) จึงมี เฟสการสำรวจความคิดเห็นสูงสุด หยุดการสำรวจเหตุการณ์เพิ่มเติม
เฟสนี้ดำเนินการโทรกลับสำหรับการดำเนินการบางอย่างของระบบ (เช่น ประเภทข้อผิดพลาด TCP) ตัวอย่างเช่น ระบบ *nix บางระบบต้องการรอให้มีการรายงานข้อผิดพลาดหากซ็อกเก็ต TCP ได้รับ ECONNREFUSED เมื่อพยายามเชื่อมต่อ สิ่งนี้จะถูกจัดคิวเพื่อดำเนินการในระหว่างขั้นตอนการติดต่อกลับที่รอดำเนินการ
โพลมีฟังก์ชั่นหลักสองอย่าง
เมื่อลูปเหตุการณ์เข้าสู่เฟสโพลและไม่มีตัวจับเวลา สองสิ่งต่อไปนี้จะเกิดขึ้น
เมื่อคิวโพลว่างเปล่า ลูปเหตุการณ์ จะตรวจสอบว่าตัวจับเวลาหมดอายุแล้วหรือไม่ หากเป็นเช่นนั้น ลูปเหตุการณ์จะถึงระยะตัวจับเวลาและดำเนินการ
ในขั้นตอนนี้ อนุญาตให้ผู้คนดำเนินการโทรกลับทันทีหลังจากเฟสโพลเสร็จสิ้น หากเฟสการโพลกลายเป็นไม่ได้ใช้งานและสคริปต์ถูกจัดคิวโดยใช้ setImmediate() ลูปเหตุการณ์อาจดำเนินการต่อไปยังเฟสตรวจสอบแทนที่จะรอ
setImmediate() จริงๆ แล้วเป็นตัวจับเวลาพิเศษที่ทำงานในเฟสที่แยกจากกันของลูปเหตุการณ์ โดยจะใช้ libuv API เพื่อกำหนดเวลาการโทรกลับที่จะดำเนินการหลังจากขั้นตอนการโพลเสร็จสิ้น
โดยทั่วไปแล้ว ขณะที่โค้ดดำเนินการ ลูปเหตุการณ์จะไปถึงขั้นตอนการสำรวจในที่สุด โดยจะรอการเชื่อมต่อขาเข้า คำขอ ฯลฯ อย่างไรก็ตาม หากมีกำหนดเวลาการโทรกลับโดยใช้ setImmediate() และระยะโพลกลายเป็นไม่ได้ใช้งาน การโทรกลับจะสิ้นสุดและดำเนินการต่อด้วยเฟสตรวจสอบ แทนที่จะรอเหตุการณ์โพล
หากซ็อกเก็ตหรือการดำเนินการถูกปิดกะทันหัน (เช่น socket.destroy()) เหตุการณ์การปิดจะถูกส่งไปยังขั้นตอนนี้ ไม่เช่นนั้นจะถูกส่งผ่าน process.nextTick() setImmediate
setImmediate() และ setTimeout( ) จะคล้ายกัน แต่จะมีพฤติกรรมแตกต่างออกไปขึ้นอยู่กับว่าเมื่อใดที่ถูกเรียก
ลำดับที่การดำเนินการโทรกลับแต่ละครั้งจะขึ้นอยู่กับ ลำดับการเรียกใช้ในสภาพแวดล้อมนี้ หากโมดูลเดียวกันถูกเรียกใช้พร้อมกัน เวลาจะถูกจำกัดโดยประสิทธิภาพของกระบวนการ (ซึ่งจะได้รับผลกระทบจากแอปพลิเคชันอื่นที่ทำงานบนเครื่องนี้ด้วย
) หากเราไม่รันสคริปต์ต่อไปนี้ใน I/O แม้ว่าจะได้รับผลกระทบจากประสิทธิภาพของกระบวนการ แต่ก็ไม่สามารถระบุลำดับการดำเนินการของตัวจับเวลาสองตัวนี้ได้:
// timeout_vs_immediate.js setTimeout(() => { console.log('หมดเวลา'); }, 0); setImmediate(() => { console.log('ทันที'); });
$ โหนด timeout_vs_immediate.js หมดเวลา ทันที $ โหนด timeout_vs_immediate.js ทันที หมดเวลา
อย่างไรก็ตาม หากคุณย้ายเข้าสู่ลูป I/O การโทรกลับทันทีจะถูกดำเนินการก่อนเสมอ
// 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 เหนือ setTimeout คือ setImmediate จะถูกดำเนินการก่อนเสมอก่อนตัวจับเวลาใดๆ ใน I/O ไม่ว่าจะมีตัวจับเวลาอยู่กี่ตัวก็ตาม
แม้ว่า process.nextTick() จะเป็นส่วนหนึ่งของ asynchronous API แต่คุณอาจสังเกตเห็นว่ามันไม่ปรากฏในไดอะแกรม เนื่องจาก process.nextTick() ไม่ได้เป็นส่วนหนึ่งของเทคโนโลยีลูปเหตุการณ์ การดำเนินการปัจจุบันจะถูกดำเนินการ หลังจากเสร็จสิ้น nextTickQueue จะถูกดำเนินการโดยไม่คำนึงถึงขั้นตอนปัจจุบันของลูปเหตุการณ์ ในที่นี้ การดำเนินการถูกกำหนดเป็นการแปลงจากตัวจัดการ C/C++ พื้นฐาน และจัดการ JavaScript ที่จำเป็นต้องดำเนินการ ตามแผนภาพ คุณสามารถเรียก process.nextTick() ได้ทุกขั้นตอน การเรียกกลับทั้งหมดที่ส่งผ่านไปยัง process.nextTick() จะถูกดำเนินการก่อนที่ event loop จะดำเนินการต่อไป ซึ่งอาจนำไปสู่สถานการณ์ที่ไม่ดีได้เนื่องจากจะทำให้คุณสามารถเรียกซ้ำได้ . process.nextTick() "อดอาหาร" I/O ของคุณ ซึ่งป้องกันไม่ให้ลูปเหตุการณ์เข้าสู่ขั้นตอนการโพล
เหตุใดสถานการณ์นี้จึงรวมอยู่ใน Node.js เนื่องจากปรัชญาการออกแบบของ Node.js คือ API ควรเป็นแบบอะซิงโครนัสเสมอ แม้ว่าจะไม่จำเป็นต้องเป็นเช่นนั้นก็ตาม โปรดดูที่
ฟังก์ชันตัวอย่างต่อไปนี้ apiCall(arg, callback) { ถ้า (ประเภทของ arg !== 'สตริง') ส่งคืนกระบวนการ nextTick ( โทรกลับ TypeError ใหม่ ('อาร์กิวเมนต์ควรเป็นสตริง') - }
ตัวอย่างข้อมูลจะทำการตรวจสอบพารามิเตอร์ และหากไม่ถูกต้อง ก็จะส่งข้อผิดพลาดไปยังการโทรกลับ API ได้รับการอัปเดตเมื่อเร็วๆ นี้เพื่อให้สามารถส่งพารามิเตอร์ไปยัง process.nextTick() ได้ ทำให้สามารถรับพารามิเตอร์ใดๆ ที่ส่งหลังจากการเรียกกลับเป็นอาร์กิวเมนต์ของการโทรกลับ ดังนั้นคุณจึงไม่จำเป็นต้องซ้อนฟังก์ชัน
สิ่งที่เรากำลังทำคือส่งข้อผิดพลาดกลับไปยังผู้ใช้ แต่เฉพาะในกรณีที่เราอนุญาตให้โค้ดที่เหลือของผู้ใช้ดำเนินการเท่านั้น เมื่อใช้ process.nextTick() เรารับรองว่า apiCall() จะเรียกใช้การเรียกกลับทุกครั้งหลังจากโค้ดผู้ใช้ที่เหลือ และก่อนที่จะอนุญาตให้ลูปเหตุการณ์ดำเนินการต่อ เพื่อให้บรรลุเป้าหมายนี้ JS call stack จะได้รับอนุญาตให้ผ่อนคลาย จากนั้น callback ที่ให้มาจะถูกดำเนินการทันที ซึ่งช่วยให้สามารถเรียกซ้ำไปที่ process.nextTick() โดยไม่ต้องกดปุ่ม RangeError: เกินขนาด call stack สูงสุด ณ เวอร์ชัน 8