మీ వెబ్ పేజీని నడిపించే JavaScript event loop, Node.js సర్వర్‌ను నడిపించే దానికంటే చాలా భిన్నంగా ఉంటుంది. మీరు జాగ్రత్తగా లేకపోతే, ఈ తేడా UIని ఫ్రీజ్ చేయవచ్చు లేదా I/O పనితీరును దెబ్బతీయవచ్చు. ఈ రెండు వాతావరణాలలో (environments) నడిచే async కోడ్‌ను రాసే ఎవరికైనా, ఇవి ఎక్కడ విభజించబడ్డాయో తెలియడం చాలా అవసరం.

ఈ తేడా ఎందుకు ముఖ్యం

Event loop అనేది ECMAScript spec ద్వారా నిర్వచించబడదు; ఇది host (బ్రౌజర్ లేదా Node.js) లో ఉంటుంది. బ్రౌజర్‌లు ఫ్రేమ్‌లను రెండర్ చేస్తున్నప్పుడు పేజీ స్పందించేలా (responsive) ఉంచాలి, అయితే Node.js నాన్-బ్లాకింగ్ I/O చుట్టూ నిర్మించబడింది. ఒక host లో పనిచేసే పద్ధతులను మరొక దానితో కలిపి వాడటం వల్ల తిరిగి కనిపెట్టలేని (hard to reproduce) బగ్‌లు రావచ్చు: ఒక పొడవైన promise chain బ్రౌజర్ యొక్క repaint ప్రక్రియను ఆపివేయవచ్చు, అదే సమయంలో సరిగ్గా నియంత్రించబడని process.nextTick లూప్ Node.js తన I/O ఫేజ్‌లకు చేరుకోకుండా అడ్డుకోవచ్చు.

బ్రౌజర్ యొక్క turn-based loop

బ్రౌజర్‌లో, ఈ లూప్ టాస్క్ ఎగ్జిక్యూషన్, microtask draining మరియు రెండరింగ్‌ను కలిపి ఒకే సైకిల్‌లో నిర్వహిస్తుంది:

  1. ఒక macrotaskను రన్ చేస్తుంది (ఉదాహరణకు: click handler, setTimeout, మొదలైనవి).
  2. అన్ని microtasksలను పూర్తి చేస్తుంది (promises, queueMicrotask).
  3. ఒకవేళ ఫ్రేమ్ సమయం అయితే, 60 fps లక్ష్యాన్ని చేరుకోవడానికి paint మరియు composite చేస్తుంది.
  4. మళ్ళీ ఇదే ప్రక్రియను పునరావృతం చేస్తుంది.

ఈ సైకిల్‌లో డెవలపర్‌లకు రెండు APIలు స్పష్టమైన అవకాశాలను ఇస్తాయి:

  • requestAnimationFrame – బ్రౌజర్ పెయింట్ చేయడానికి సరిగ్గా ముందు దీనిని పిలుస్తారు. యానిమేషన్ పనులకు ఇది సరైన చోటు, ఎందుకంటే ఈ callback ప్రస్తుత microtasks తర్వాత కానీ, తదుపరి ఫ్రేమ్ కంటే ముందే రన్ అవుతుంది.
  • requestIdleCallback – బ్రౌజర్‌కు అత్యవసరమైన పనులు లేనప్పుడు దీనిని పిలుస్తారు. అనలిటిక్స్ లేదా డేటా ప్రీ-లోడింగ్ వంటి తక్కువ ప్రాధాన్యత కలిగిన పనులకు ఇది ఉపయోగపడుతుంది.

Pitfall: microtask starvation

బ్రౌజర్ రెండర్ చేయడానికి ముందు microtask క్యూను ఖాళీ చేస్తుంది కాబట్టి, ఒక పొడవైన promise chain వల్ల UI ఎప్పటికీ పెయింట్ కాకుండా పోవచ్చు. ఇక్కడ call stack బ్లాక్ అవ్వదు; పేజీ కేవలం రెండర్ దశకు చేరుకోదు, ఇది యూజర్‌కు స్క్రీన్ ఫ్రీజ్ అయినట్లు అనిపిస్తుంది.

Node యొక్క libuv-driven loop

Node.js తన లూప్‌ను libuvకి అప్పగిస్తుంది. ఇది ఒక C లైబ్రరీ, ఇది పనిని వేర్వేరు ఫేజ్‌లుగా విభజిస్తుంది, ప్రతి ఫేజ్‌కు దాని స్వంత క్యూ ఉంటుంది:

  1. TimerssetTimeout మరియు setInterval నుండి వచ్చే callbacks.
  2. Pending callbacks – OS స్థాయిలో ఇప్పటికే పూర్తయిన deferred I/O callbacks.
  3. Poll – కొత్త I/O ఈవెంట్‌లను (file reads, network data) సేకరిస్తుంది.
  4. ChecksetImmediate callbacksను రన్ చేస్తుంది.
  5. Close callbacks – ఒక socket లేదా handle క్లోజ్ అయినప్పుడు రన్ అవుతుంది.

ఈ ఫేజ్ ఆర్డర్ వెలుపల రెండు నిర్మాణాలు ఉంటాయి:

  • process.nextTick – ప్రస్తుత ఆపరేషన్ పూర్తయిన వెంటనే, microtask క్యూ కంటే ముందు ఇది రన్ అవుతుంది.

Pitfall: I/O starvation

ఒక ఫంక్షన్ పదేపదే వేరే పనులకు అవకాశం ఇవ్వకుండా (yielding) process.nextTickను షెడ్యూల్ చేస్తూ ఉంటే, Node ఎప్పటికీ "next-tick" దశను దాటి ముందుకు వెళ్లదు. దీనివల్ల నెట్‌వర్క్ రిక్వెస్ట్‌లు, ఫైల్ రీడ్స్ మరియు టైమర్‌లు నిలిచిపోతాయి, ఇది సర్వర్-సైడ్ లేటెన్సీ స్పైక్‌లకు లేదా సర్వర్ పూర్తిగా హ్యాంగ్ అవ్వడానికి కారణమవుతుంది.

setImmediate vs. setTimeout ప్రాక్టికల్‌గా

రెండూ తదుపరి ఇటరేషన్ కోసం callbacksను షెడ్యూల్ చేస్తాయి, కానీ వాటి క్రమం అవి ఎక్కడ పిలవబడ్డాయి అనే దానిపై ఆధారపడి ఉంటుంది:

  • Top-level code – వీటి క్రమం ఖచ్చితంగా ఉండదు; ఇది ప్రాసెస్ ఎంత వేగంగా స్టార్ట్ అవుతుంది అనే దానిపై ఆధారపడి ఉంటుంది.
  • Inside an I/O callback – వీటి క్రమం ఖచ్చితంగా (deterministic) ఉంటుంది: setImmediate, setTimeout(fn, 0) కంటే ముందే రన్ అవుతుంది. Poll phase పూర్తయిన తర్వాత, libuv మళ్ళీ zero-delay timeout కోసం Timers phaseలోకి వెళ్లేముందు, Check phase (ఇక్కడ setImmediate ఉంటుంది)కి మారుతుంది.

రీడ్ పూర్తయిన వెంటనే ఒక రిసోర్స్‌ను క్లీన్ చేయడం వంటి ఖచ్చితమైన సీక్వెన్సింగ్ అవసరమైనప్పుడు ఈ సూక్ష్మతేడా చాలా ముఖ్యం.

క్లుప్తంగా ముఖ్యమైన తేడాలు

  • Goal: బ్రౌజర్‌లు విజువల్ అప్‌డేట్‌లకు ప్రాధాన్యత ఇస్తాయి; Node I/O రెడీనెస్ (readiness) కు ప్రాధాన్యత ఇస్తుంది.
  • Rendering hook: requestAnimationFrame (బ్రౌజర్‌లో మాత్రమే).
  • Phase-specific hook: setImmediate (Nodeలో మాత్రమే, Check phaseలో రన్ అవుతుంది).
  • High-priority queue: process.nextTick (Nodeలో మాత్రమే, microtasks కంటే ముందు రన్ అవుతుంది).
  • Starvation risk: బ్రౌజర్‌లలో పొడవైన promise chains; Nodeలో అపరిమితమైన process.nextTick.

తదుపరి ఏమి గమనించాలి?

మీరు రెండు వాతావరణాలలో (ఉదాహరణకు, isomorphic libraries) నడిచే కోడ్‌బేస్‌ను నిర్వహిస్తుంటే, ఈ క్రింది సందర్భాలను తనిఖీ చేయండి:

  • ఈవెంట్ లూప్‌కు అవకాశం ఇవ్వకుండా చాలాే ఎక్కువ promisesలను చైన్ చేయడం. రెండరర్‌కు అవకాశం ఇవ్వడానికి await new Promise(r => setTimeout(r, 0))ను ఇన్సర్ట్ చేయండి లేదా బ్రౌజర్‌లో requestIdleCallbackను ఉపయోగించండి.
  • వాయిదా వేయగలిగే పనుల కోసం process.nextTickను ఉపయోగించడం. మీకు “next-tick” అత్యవసరత అవసరం లేనప్పుడు, setImmediate లేదా సాధారణ promiseను ఉపయోగించడం ఉత్తమం.
  • setTimeout(fn, 0) మరియు setImmediate ఒకేలా పనిచేస్తాయని అనుకోవడం. సీక్వెన్స్ ముఖ్యం అనుకున్నప్పుడు, I/O callbacks లోపల వాటి ఆర్డర్‌ను పరీక్షించండి.

సారాంశం

ఈవెంట్ లూప్ అనేది హోస్ట్-నిర్దిష్ట షెడ్యూలర్, ఇది యూనివర్సల్ JavaScript ఫీచర్ కాదు. బ్రౌజర్‌లు రెండరింగ్‌ను లూప్‌లో విలీనం చేస్తాయి; Node, I/Oని libuv ఫేజ్‌లుగా వేరు చేస్తుంది. ప్రాధాన్యత మెకానిజమ్‌లను—బ్రౌజర్‌లో microtasks, Nodeలో process.nextTick—తప్పుగా ఉపయోగించడం వల్ల, ఆ ఎన్విరాన్మెంట్ ఏ పని కోసం నిర్మించబడిందో, ఆ సిస్టమ్ భాగానికి అంతరాయం కలిగించవచ్చు. మీ async ప్యాటర్న్స్‌ను హోస్ట్ యొక్క లూప్ మోడల్‌కు అనుగుణంగా ఉంచుకోవడం ద్వారా, మీరు ఫ్రీజ్ అయిన పేజీలను మరియు బ్లాక్ అయిన సర్వర్‌లను నివారించవచ్చు.