ನಿಮ್ಮ ವೆಬ್ ಪುಟವನ್ನು ನಿಯಂತ್ರಿಸುವ JavaScript event loop ಮತ್ತು Node.js ಸರ್ವರ್ ಅನ್ನು ನಿಯಂತ್ರಿಸುವ event loop ನಡುವೆ ಬಹಳ ವ್ಯತ್ಯಾಸವಿದೆ. ನೀವು ಎಚ್ಚರಿಕೆಯಿಂದ ಇಲ್ಲದಿದ್ದರೆ, ಈ ವ್ಯತ್ಯಾಸವು UI ಅನ್ನು ಸ್ಥಗಿತಗೊಳಿಸಬಹುದು (freeze) ಅಥವಾ I/O ಅನ್ನು ಕುಂಠಿತಗೊಳಿಸಬಹುದು. ಎರಡೂ ಪರಿಸರಗಳಲ್ಲಿ (environments) ಚಲಿಸುವ async ಕೋಡ್ ಬರೆಯುವವರಿಗೆ ಇವೆರಡರ ನಡುವಿನ ವ್ಯತ್ಯಾಸವನ್ನು ತಿಳಿಯುವುದು ಅತ್ಯಗತ್ಯ.
ಈ ವ್ಯತ್ಯಾಸ ಏಕೆ ಮುಖ್ಯ?
Event loop ಅನ್ನು ECMAScript ಸ್ಪೆಕ್ (spec) ನಿಂದ ವ್ಯಾಖ್ಯಾನಿಸಲಾಗಿಲ್ಲ; ಇದು host ನಲ್ಲಿ ಇರುತ್ತದೆ. ಬ್ರೌಸರ್ಗಳು ಫ್ರೇಮ್ಗಳನ್ನು ರೆಂಡರ್ ಮಾಡುವಾಗ ಪುಟವು ಪ್ರತಿಕ್ರಿಯಾತ್ಮಕವಾಗಿರುವಂತೆ (responsive) ನೋಡಿಕೊಳ್ಳಬೇಕು, ಆದರೆ Node.js ಅನ್ನು non-blocking I/O ಸುತ್ತ ನಿರ್ಮಿಸಲಾಗಿದೆ. ಒಂದು host ನಲ್ಲಿ ಕೆಲಸ ಮಾಡುವ ಪ್ಯಾಟರ್ನ್ಗಳನ್ನು ಇನ್ನೊಂದರೊಂದಿಗೆ ಬೆರೆಸುವುದರಿಂದ ಪತ್ತೆಹಚ್ಚಲು ಕಷ್ಟವಾದ ಬಗ್ಗಳು (bugs) ಉಂಟಾಗಬಹುದು: ಒಂದು ಉದ್ದನೆಯ promises ಸರಪಳಿಯು ಬ್ರೌಸರ್ನ repaint ಅನ್ನು ತಡೆಹಿಡಿಯಬಹುದು, ಆದರೆ ಅನ್ಚೆಕ್ ಮಾಡದ process.nextTick loop Node ಅನ್ನು ಅದರ I/O ಹಂತಗಳಿಗೆ ತಲುಪದಂತೆ ತಡೆಯಬಹುದು.
ಬ್ರೌಸರ್ನ ಟರ್ನ್-ಬೇಸ್ಡ್ ಲೂಪ್ (turn-based loop)
ಬ್ರೌಸರ್ನಲ್ಲಿ, ಲೂಪ್ ಒಂದು ಏಕೈಕ ಸೈಕಲ್ ಅನ್ನು ರನ್ ಮಾಡುತ್ತದೆ, ಇದು task execution, microtask draining ಮತ್ತು rendering ಅನ್ನು ಅಂತರಾಳದಲ್ಲಿ (interleave) ನಡೆಸುತ್ತದೆ:
- ಒಂದು macrotask ಅನ್ನು ರನ್ ಮಾಡಿ (ಉದಾಹರಣೆಗೆ click handler,
setTimeoutಇತ್ಯಾದಿ). - ಎಲ್ಲಾ microtasks ಅನ್ನು ಖಾಲಿ ಮಾಡಿ (promises,
queueMicrotask). - ಒಂದು ಫ್ರೇಮ್ ಬರಬೇಕಿದ್ದರೆ, 60 fps ಗುರಿಯನ್ನು ತಲುಪಲು paint ಮತ್ತು composite ಮಾಡಿ.
- ಪುನರಾವರ್ತಿಸಿ.
ಎರಡು APIಗಳು ಈ ಸೈಕಲ್ಗೆ ಡೆವಲಪರ್ಗಳಿಗೆ ಸ್ಪಷ್ಟವಾದ ಹೂಕ್ಗಳನ್ನು (hooks) ನೀಡುತ್ತವೆ:
requestAnimationFrame– ಬ್ರೌಸರ್ ಪೇಂಟ್ ಮಾಡುವ ಮೊದಲು ಇದನ್ನು ಕರೆಯಲಾಗುತ್ತದೆ. ಅನಿಮೇಷನ್ ಕೆಲಸಗಳಿಗೆ ಇದು ಸರಿಯಾದ ಸ್ಥಳವಾಗಿದೆ ಏಕೆಂದರೆ ಈ callback ಪ್ರಸ್ತುತ microtasks ನಂತರ ಆದರೆ ಮುಂದಿನ ಫ್ರೇಮ್ಗಿಂತ ಮೊದಲು ರನ್ ಆಗುತ್ತದೆ.requestIdleCallback– ಬ್ರೌಸರ್ನಲ್ಲಿ ಯಾವುದೇ ಹೆಚ್ಚಿನ ಆದ್ಯತೆಯ ಕೆಲಸವಿಲ್ಲದಿದ್ದಾಗ ಇದನ್ನು ಕರೆಯಲಾಗುತ್ತದೆ. ಅನಾಲಿಟಿಕ್ಸ್ ಅಥವಾ ಡೇಟಾ ಪ್ರೀ-ಲೋಡಿಂಗ್ನಂತಹ ಕಡಿಮೆ ಪ್ರಭಾವ ಬೀರುವ ಕಾರ್ಯಗಳಿಗೆ ಇದು ಉಪಯುಕ್ತವಾಗಿದೆ.
ಅಪಾಯ: microtask starvation
ಬ್ರೌಸರ್ ರೆಂಡರ್ ಮಾಡುವ ಮೊದಲು microtask queue ಅನ್ನು ಖಾಲಿ ಮಾಡುವುದರಿಂದ, ಉದ್ದನೆಯ promises ಸರಪಳಿಯು UI ಪೇಂಟ್ ಆಗದಂತೆ ತಡೆಯಬಹುದು. ಇಲ್ಲಿ call stack ಬ್ಲಾಕ್ ಆಗುವುದಿಲ್ಲ; ಬದಲಾಗಿ ಪುಟವು ರೆಂಡರ್ ಹಂತವನ್ನು ತಲುಪುವುದಿಲ್ಲ, ಇದು ಬಳಕೆದಾರರಿಗೆ ಫ್ರೀಜ್ ಆದಂತೆ ಕಾಣಿಸುತ್ತದೆ.
Node ನ libuv-ಚಾಲಿತ ಲೂಪ್
Node.js ತನ್ನ ಲೂಪ್ ಅನ್ನು libuv ಗೆ ವಹಿಸುತ್ತದೆ. ಇದು ಒಂದು C ಲೈಬ್ರರಿಯಾಗಿದ್ದು, ಕೆಲಸವನ್ನು ವಿಭಿನ್ನ ಹಂತಗಳಾಗಿ ವಿಂಗಡಿಸುತ್ತದೆ ಮತ್ತು ಪ್ರತಿಯೊಂದು ಹಂತಕ್ಕೂ ತನ್ನದೇ ಆದ ಕ್ಯೂ (queue) ಇರುತ್ತದೆ:
- Timers –
setTimeoutಮತ್ತುsetIntervalನಿಂದ ಬರುವ callbacks. - Pending callbacks – OS ಮಟ್ಟದಲ್ಲಿ ಈಗಾಗಲೇ ಪೂರ್ಣಗೊಂಡಿರುವ ವಿಳಂಬಿತ I/O callbacks.
- Poll – ಹೊಸ I/O ಇವೆಂಟ್ಗಳನ್ನು (file reads, network data) ಪಡೆಯುತ್ತದೆ.
- Check –
setImmediatecallbacks ಅನ್ನು ರನ್ ಮಾಡುತ್ತದೆ. - Close callbacks – ಸಾಕೆಟ್ ಅಥವಾ ಹ್ಯಾಂಡಲ್ ಮುಚ್ಚಿದಾಗ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ.
ಎರಡು ರಚನೆಗಳು (constructs) ಈ ಹಂತದ ಕ್ರಮದ ಹೊರಗೆ ಇರುತ್ತವೆ:
process.nextTick– ಪ್ರಸ್ತುತ ಆಪರೇಷನ್ ಮುಗಿದ ತಕ್ಷಣ, microtask queue ಗಿಂತ ಮೊದಲು ರನ್ ಆಗುತ್ತದೆ.
ಅಪಾಯ: I/O starvation
ಒಂದು ಫಂಕ್ಷನ್ ಯೆಲ್ಡಿಂಗ್ (yielding) ಮಾಡದೆ ಪದೇ ಪದೇ process.nextTick ಅನ್ನು ಶೆಡ್ಯೂಲ್ ಮಾಡಿದರೆ, Node ಎಂದಿಗೂ "next-tick" ಹಂತದ ನಂತರಕ್ಕೆ ಸಾಗುವುದಿಲ್ಲ. ನೆಟ್ವರ್ಕ್ ರಿಕ್ವೆಸ್ಟ್ಗಳು, ಫೈಲ್ ರೀಡ್ಗಳು ಮತ್ತು ಟೈಮರ್ಗಳು ನಿಷ್ಕ್ರಿಯವಾಗಿರುತ್ತವೆ, ಇದು ಸರ್ವರ್-ಸೈಡ್ ವಿಳಂಬ (latency spikes) ಅಥವಾ ಸಂಪೂರ್ಣವಾಗಿ ಹ್ಯಾಂಗ್ ಆಗಲು ಕಾರಣವಾಗಬಹುದು.
ಪ್ರಾಯೋಗಿಕವಾಗಿ setImmediate vs. setTimeout
ಎರಡೂ ಮುಂದಿನ ಇಟರೇಶನ್ಗಾಗಿ callbacks ಅನ್ನು ಶೆಡ್ಯೂಲ್ ಮಾಡುತ್ತವೆ, ಆದರೆ ಅವುಗಳನ್ನು ಎಲ್ಲಿ ಕರೆಯಲಾಗುತ್ತದೆ ಎಂಬುದರ ಮೇಲೆ ಅವುಗಳ ಕ್ರಮ ನಿರ್ಧರಿಸಲ್ಪಡುತ್ತದೆ:
- Top-level code – ಕ್ರಮವು ಖಚಿತವಾಗಿರುವುದಿಲ್ಲ; ಇದು ಪ್ರೊಸೆಸ್ ಎಷ್ಟು ವೇಗವಾಗಿ ಪ್ರಾರಂಭವಾಗುತ್ತದೆ ಎಂಬುದರ ಮೇಲೆ ಅವಲಂಬಿತವಾಗಿರುತ್ತದೆ.
- Inside an I/O callback – ಕ್ರಮವು ನಿರ್ಣಾಯಕವಾಗಿರುತ್ತದೆ (deterministic):
setImmediateಎಂಬುದುsetTimeout(fn, 0)ಗಿಂತ ಮೊದಲು ರನ್ ಆಗುತ್ತದೆ. Poll ಹಂತವು ಮುಗಿದ ನಂತರ, libuv ಶೂನ್ಯ-ವಿಳಂಬದ (zero-delay) ಟೈಮೌಟ್ಗಾಗಿ Timers ಹಂತಕ್ಕೆ ಮರುಪ್ರವೇಶಿಸುವ ಮೊದಲು Check ಹಂತಕ್ಕೆ (ಇಲ್ಲಿsetImmediateಇರುತ್ತದೆ) ಚಲಿಸುತ್ತದೆ.
ರೀಡ್ (read) ಪೂರ್ಣಗೊಂಡ ತಕ್ಷಣ ಸಂಪನ್ಮೂಲವನ್ನು (resource) ಕ್ಲೀನ್ ಅಪ್ ಮಾಡುವಂತಹ ನಿಖರವಾದ ಅನುಕ್ರಮದ ಅಗತ್ಯವಿದ್ದಾಗ ಈ ಸೂಕ್ಷ್ಮತೆ ಮುಖ್ಯವಾಗುತ್ತದೆ.
ಪ್ರಮುಖ ವ್ಯತ್ಯಾಸಗಳು ಒಮ್ಮೆ ನೋಡಿ
- Goal (ಗುರಿ): ಬ್ರೌಸರ್ಗಳು ದೃಶ್ಯ ಅಪ್ಡೇಟ್ಗಳಿಗೆ ಆದ್ಯತೆ ನೀಡುತ್ತವೆ; Node I/O ಸಿದ್ಧತೆಗೆ ಆದ್ಯತೆ ನೀಡುತ್ತದೆ.
- Rendering hook:
requestAnimationFrame(ಬ್ರೌಸರ್ ಮಾತ್ರ). - Phase-specific hook:
setImmediate(Node ಮಾತ್ರ, Check ಹಂತದಲ್ಲಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ). - High-priority queue:
process.nextTick(Node ಮಾತ್ರ, microtasks ಗಿಂತ ಮೊದಲು ರನ್ ಆಗುತ್ತದೆ). - Starvation risk: ಬ್ರೌಸರ್ಗಳಲ್ಲಿ ಉದ್ದನೆಯ promise ಸರಪಳಿಗಳು; Node ನಲ್ಲಿ ಅನ್ಬೌಂಡೆಡ್ (unbounded)
process.nextTick.
ಮುಂದೆ ಏನು ಗಮನಿಸಬೇಕು
ನೀವು ಎರಡೂ ಪರಿಸರಗಳಲ್ಲಿ ಚಲಿಸುವ ಕೋಡ್ ಬೇಸ್ ಅನ್ನು ನಿರ್ವಹಿಸುತ್ತಿದ್ದರೆ (ಉದಾಹರಣೆಗೆ, isomorphic libraries), ಈ ಕೆಳಗಿನವುಗಳನ್ನು ಪರಿಶೀಲಿಸಿ:
- Event loop ಗೆ ಯೆಲ್ಡ್ ಮಾಡದೆ ಅನೇಕ promises ಅನ್ನು ಚೇನ್ ಮಾಡಬೇಡಿ. ರೆಂಡರರ್ಗೆ ಅವಕಾಶ ನೀಡಲು
await new Promise(r => setTimeout(r, 0))ಅನ್ನು ಸೇರಿಸಿ ಅಥವಾ ಬ್ರೌಸರ್ನಲ್ಲಿrequestIdleCallbackಬಳಸಿ. - ವಿಳಂಬ ಮಾಡಬಹುದಾದ ಕೆಲಸಗಳಿಗಾಗಿ
process.nextTickಅನ್ನು ಬಳಸಬೇಡಿ. ನಿಮಗೆ "next-tick" ತುರ್ತು ಅಗತ್ಯವಿಲ್ಲದಿದ್ದಾಗsetImmediateಅಥವಾ ಸಾಮಾನ್ಯ promise ಅನ್ನು ಆದ್ಯತೆಯಾಗಿ ಬಳಸಿ. setTimeout(fn, 0)ಮತ್ತುsetImmediateಒಂದೇ ಎಂಬ ತಪ್ಪು ಕಲ್ಪನೆ ಇಟ್ಟುಕೊಳ್ಳಬೇಡಿ. ಕ್ರಮವು ಮುಖ್ಯವಾಗಿದ್ದರೆ, I/O callbacks ಒಳಗಿನ ಆರ್ಡರ್ ಅನ್ನು ಪರೀಕ್ಷಿಸಿ.
ಸಾರಾಂಶ
event loop ಎಂಬುದು ಹೋಸ್ಟ್-ನಿರ್ದಿಷ್ಟ ಶೆಡ್ಯೂಲರ್ ಆಗಿದೆಯೇ ಹೊರತು, ಇದು ಸಾರ್ವತ್ರಿಕ JavaScript ವೈಶಿಷ್ಟ್ಯವಲ್ಲ. ಬ್ರೌಸರ್ಗಳು ರೆಂಡರಿಂಗ್ ಅನ್ನು ಲೂಪ್ನಲ್ಲಿ ಅಳವಡಿಸುತ್ತವೆ; Node ಎಂಬುದು I/O ಅನ್ನು libuv ಹಂತಗಳಿಗೆ ಪ್ರತ್ಯೇಕಿಸುತ್ತದೆ. ಆದ್ಯತೆಯ ಕಾರ್ಯವಿಧಾನಗಳನ್ನು—ಬ್ರೌಸರ್ನಲ್ಲಿ microtasks ಮತ್ತು Node ನಲ್ಲಿ process.nextTick—ತಪ್ಪಾಗಿ ಬಳಸುವುದರಿಂದ, ಪ್ರತಿಯೊಂದು ಎನ್ವಿರಾನ್ಮೆಂಟ್ ಯಾವ ಉದ್ದೇಶಕ್ಕಾಗಿ ನಿರ್ಮಿತವಾಗಿದೆಯೋ, ಆ ಸಿಸ್ಟಮ್ನ ಭಾಗಕ್ಕೆ ಸಂಪನ್ಮೂಲಗಳ ಕೊರತೆ ಉಂಟಾಗಬಹುದು. ನಿಮ್ಮ async ಪ್ಯಾಟರ್ನ್ಗಳನ್ನು ಹೋಸ್ಟ್ನ ಲೂಪ್ ಮಾಡೆಲ್ಗೆ ಅನುಗುಣವಾಗಿ ಹೊಂದಿಸಿಕೊಳ್ಳಿ, ಆಗ ನೀವು ಫ್ರೀಜ್ ಆದ ಪೇಜ್ಗಳು ಮತ್ತು ಬ್ಲಾಕ್ ಆದ ಸರ್ವರ್ಗಳಂತಹ ಸಮಸ್ಯೆಗಳನ್ನು ತಪ್ಪಿಸಬಹುದು.
