JavaScript Event Loop: Call Stack, Microtask, Macrotask এবং মাঝখানের সবকিছু

JavaScript একই সঙ্গে timer চালু করতে পারে, network থেকে data চাইতে পারে, click-এর response দিতে পারে এবং screen update করতে পারে। অথচ browser-এর main thread-এ সাধারণত এক মুহূর্তে একটি JavaScript কাজই execute হয়।
বিষয়টি পরস্পরবিরোধী মনে হয়, যদি “JavaScript” আর “JavaScript runtime”-কে একই জিনিস ভাবা হয়। আসলে তারা এক নয়।
JavaScript engine ভাষার instruction execute করে। Browser timer, networking, DOM event, rendering এবং Event Loop দেয়—যেটি ঠিক করে কখন কোন JavaScript callback চলতে পারবে। Node.js আবার নিজস্ব I/O system ও Event Loop phase-সহ ভিন্ন একটি host environment দেয়। Engine ও host-এর এই সহযোগিতা থেকেই asynchronous JavaScript তৈরি হয়।
এই লেখায় আমরা এমন একটি mental model তৈরি করব, যা দিয়ে নিচের প্রশ্নগুলোর উত্তর অনুমান নয়, যুক্তি দিয়ে বের করা যাবে:
setTimeout(fn, 0)সঙ্গে সঙ্গে চলে না কেন?- resolved Promise কেন timer-এর আগে চলে?
await-এর পরের code কোথায় যায়?- শেষ না হওয়া Promise chain কীভাবে page freeze করে দিতে পারে?
- Browser কখন screen paint করার সুযোগ পায়?
- Node.js-এ callback-এর order browser থেকে আলাদা হতে পারে কেন?
JavaScript engine-এর ভেতরের execution pipeline পরিষ্কার না থাকলে আগে পড়ে নিন JavaScript চললে আসলে কী ঘটে? Source Code থেকে Execution পর্যন্ত। সেখানে source code থেকে parsing, bytecode, optimization ও execution পর্যন্ত যাত্রা ব্যাখ্যা করা হয়েছে। এই লেখা শুরু হচ্ছে সেই জায়গা থেকে, যেখানে executable JavaScript-কে runtime-এর অন্য সব কাজের সঙ্গে সময় ভাগ করতে হয়।
সবচেয়ে ছোট কিন্তু কার্যকর mental model
Browser JavaScript-এর জন্য এই cycle-টি মনে রাখুন:
- একটি task বেছে নিয়ে চালানো হয়।
- Call Stack খালি না হওয়া পর্যন্ত সব synchronous function call চলতে থাকে।
- Microtask queue সম্পূর্ণ খালি করা হয়।
- Browser rendering update করতে পারে।
- Cycle আবার শুরু হয়।
সংক্ষেপে:
একটি task
-> synchronous call stack
-> queue-তে থাকা সব microtask
-> সম্ভাব্য rendering opportunity
-> পরের taskApplication-level অধিকাংশ আচরণ বোঝার জন্য এই model যথেষ্ট। তবে আসল platform আরও সূক্ষ্ম। HTML Standard অনুযায়ী একাধিক task queue ও task source থাকতে পারে। Browser runnable task-এর মধ্য থেকে একটি বেছে নিতে পারে, যদিও একই task source-এর ভেতরের order রক্ষা করতে হয়। তাই “একটি macrotask queue” শেখার সুবিধার জন্য ভালো shorthand, কিন্তু প্রতিটি browser implementation-এর হুবহু নকশা নয়।
Engine, host এবং Event Loop
Event Loop JavaScript ভাষার ভেতরে লুকানো কোনো জাদুকরি অংশ নয়। ECMAScript execution context ও Job define করে; Promise reaction চালানোর Job-ও এর মধ্যে পড়ে। Browser বা Node.js-এর মতো host এই language mechanism-কে timer, I/O, user input এবং scheduling-এর সঙ্গে যুক্ত করে।
একটি সরল browser runtime-এ সাধারণত নিচের অংশগুলো থাকে:
| অংশ | দায়িত্ব |
|---|---|
| JavaScript engine | JavaScript parse ও execute করা; execution context ও memory পরিচালনা করা |
| Call Stack | এই মুহূর্তে কোন function chain execute হচ্ছে তা ধরে রাখা |
| Web APIs | Timer, networking, DOM event, observer এবং browser capability দেওয়া |
| Task queues | Timer callback ও event dispatch-এর মতো runnable কাজ রাখা |
| Microtask queue | Promise reaction-এর মতো অগ্রাধিকারপ্রাপ্ত follow-up কাজ রাখা |
| Rendering pipeline | Style, layout, paint ও composite frame প্রস্তুত করা |
| Event Loop | Task, microtask ও rendering কখন এগোবে তা সমন্বয় করা |
MDN-এর JavaScript execution model-ও engine ও host environment-এর এই আলাদা দায়িত্ব ব্যাখ্যা করে। Boundary-টি গুরুত্বপূর্ণ: Promise JavaScript-এর অংশ, কিন্তু setTimeout, fetch ও DOM host API।
Call Stack: এই মুহূর্তে কী চলছে?
JavaScript কোনো function-এ ঢুকলে engine Call Stack-এ একটি frame push করে। Function return করলে frame-টি pop হয়। Stack last-in, first-out হওয়ায় সবার শেষে ঢোকা function সবার আগে শেষ হয়।
function getLabel() {
return "Event loop";
}
function printLabel() {
console.log(getLabel());
}
printLabel();Stack মোটামুটি এভাবে বদলায়:
global script
global script -> printLabel
global script -> printLabel -> getLabel
global script -> printLabel
global script
emptyএখান থেকে দুটি মৌলিক নিয়ম পাওয়া যায়।
প্রথমত, প্রতিটি Job বা callback-এর ক্ষেত্রে JavaScript run-to-completion model ব্যবহার করে। একই thread-এ একটি function অর্ধেক চলার সময় অন্য task এসে ইচ্ছামতো সেটিকে থামিয়ে JavaScript চালাতে পারে না। বর্তমান কাজ return করা এবং stack unwind হওয়া পর্যন্ত control ধরে রাখে।
দ্বিতীয়ত, timer-এর সময় শেষ বা network request complete হলেই asynchronous callback চলতে পারে না। বর্তমান stack খালি হওয়া এবং host-এর scheduling-এর জন্য তাকে অপেক্ষা করতে হয়।
Run-to-completion local code বোঝা সহজ করে, কিন্তু main-thread performance problem-ও তৈরি করে। কোনো function 500 ms চললে click handler, timer ও rendering-কে প্রায় একই সময় অপেক্ষা করতে হয়।
Task—যাকে সাধারণভাবে Macrotask বলা হয়
HTML specification-এর আনুষ্ঠানিক নাম task। “Macrotask” একটি informal term, যা task ও microtask-এর পার্থক্য বোঝাতে প্রচলিত হয়েছে।
Browser task-এর সাধারণ source হলো:
- প্রথমবার কোনো script execute করা;
- click, keyboard বা অন্য DOM event dispatch করা;
- ready হয়ে যাওয়া
setTimeoutবাsetIntervalcallback চালানো; - নির্দিষ্ট networking ও messaging event process করা;
MessageChannelmessage handle করা।
Event Loop একটি নির্বাচিত task পুরোপুরি শেষ করে। Task শেষ হওয়ার পরেই browser microtask checkpoint চালাতে, render করতে বা আরেকটি task নিতে পারে।
setTimeout(fn, 0) তাৎক্ষণিক নয় কেন
setTimeout মূলত দুটি কাজ করে:
- অন্তত requested delay পর্যন্ত অপেক্ষা করতে host-কে বলে।
- Timer eligible হলে callback-টিকে একটি task হিসেবে queue করায়।
এটি callback-কে Call Stack-এ push করে না। Delay-টিও নিশ্চিত execution time নয়।
console.log("first");
setTimeout(() => {
console.log("timer");
}, 0);
console.log("second");Output:
first
second
timerGlobal script নিজেই তখন current task, তাই সেটি আগে শেষ হবে। এরপর runtime microtask drain করবে, হয়তো render করবে, তারপর কোনো এক সময় timer task বেছে নেবে। Main thread ব্যস্ত থাকা, আগে থেকে queue-তে থাকা কাজ, operating-system scheduling, nested timer clamping এবং background-tab throttling callback-কে আরও দেরি করাতে পারে। MDN-এর setTimeout() documentation-এ এই কারণগুলো বিস্তারিত আছে।
Delay-কে ভাবুন এই সময়ের আগে নয় হিসেবে; ঠিক এই সময়েই হিসেবে নয়।
Microtask: পরের task-এর আগের follow-up কাজ
বর্তমান JavaScript শেষ হওয়ার পরে, কিন্তু runtime অন্য task-এ যাওয়ার আগে যে ছোট follow-up কাজ হওয়া দরকার, তার জন্য Microtask।
Microtask-এর সাধারণ source হলো:
.then(),.catch()বা.finally()দিয়ে register করা Promise reaction handler;await-এর পরেasyncfunction-এর continuation;queueMicrotask()-এ দেওয়া callback;- Browser-এর
MutationObservercallback।
কোনো task Call Stack খালি করে দিলে Event Loop একটি microtask checkpoint চালায়। সবচেয়ে পুরোনো microtask থেকে শুরু করে একে একে চালায়, queue খালি না হওয়া পর্যন্ত। একটি microtask যদি আরেকটি microtask queue করে, নতুনটিও একই checkpoint-এ, পরের task-এর আগেই চলে।
এই নিয়মের কারণেই microtask-কে timer-এর চেয়ে “higher priority” মনে হয়। আবার এই নিয়মের কারণেই অসতর্ক microtask recursion runtime-এর অন্য কাজকে starve করতে পারে।
console.log("A");
setTimeout(() => console.log("task"), 0);
Promise.resolve().then(() => console.log("promise microtask"));
queueMicrotask(() => console.log("queued microtask"));
console.log("B");Output:
A
B
promise microtask
queued microtask
taskPromise আগে থেকেই fulfilled ছিল, তবু .then() synchronousভাবে চলেনি। এটি একটি reaction queue করেছে। সেই reaction explicit queueMicrotask-এর আগে queue হওয়ায় microtask queue-এর first-in order তাদের অবস্থান ধরে রেখেছে।
Scheduling edge case specification অনুযায়ী যাচাই করতে MDN microtask guide এবং HTML Standard-এর microtask checkpoint algorithm সবচেয়ে ভালো reference।
Promise executor synchronous, কিন্তু handler নয়
এই পার্থক্য না জানলে execution order প্রায়ই ভুল হয়:
console.log("start");
new Promise((resolve) => {
console.log("executor");
resolve();
}).then(() => {
console.log("handler");
});
console.log("end");Output:
start
executor
end
handlerPromise constructor-এ দেওয়া function সঙ্গে সঙ্গে execute হয়। resolve() Promise-এর state বদলায় এবং reaction schedule করার ব্যবস্থা করে; এটি .then() handler-কে synchronousভাবে call করে না। বর্তমান stack খালি হলে handler microtask হিসেবে চলে।
Microtask আরও Microtask schedule করতে পারে
queueMicrotask(() => {
console.log("microtask 1");
queueMicrotask(() => {
console.log("microtask 3");
});
});
queueMicrotask(() => {
console.log("microtask 2");
});Output:
microtask 1
microtask 2
microtask 3নতুন callback microtask queue-এর শেষে যায়। কিন্তু Event Loop নতুন task বেছে নেওয়ার আগেই এটিসহ পুরো queue drain করে।
একটি সম্পূর্ণ execution-order walkthrough
ব্যাখ্যা পড়ার আগে output অনুমান করুন:
console.log("1: script start");
setTimeout(() => {
console.log("6: timer");
}, 0);
Promise.resolve()
.then(() => {
console.log("3: promise one");
})
.then(() => {
console.log("5: promise two");
});
queueMicrotask(() => {
console.log("4: explicit microtask");
});
console.log("2: script end");Output হবে:
1: script start
2: script end
3: promise one
4: explicit microtask
5: promise two
6: timerTimeline-টি ধাপে ধাপে দেখা যাক:
- Initial script একটি task হিসেবে চলে এবং
1log করে। setTimeouttimer register করে। Callback current task-এ চলতে পারে না।- প্রথম
.then()-এর Promise fulfilled হওয়ায় তার reaction microtask queue-তে যায়। queueMicrotaskতার callback-টি প্রথম reaction-এর পেছনে যোগ করে।- Script
2log করে শেষ হয়। - প্রথম Promise reaction
3log করে। এটি return করলে প্রথম.then()থেকে তৈরি Promise fulfilled হয়, ফলে দ্বিতীয় reaction queue-তে আগে থেকে অপেক্ষমাণ microtask-এর পেছনে যোগ হয়। - Explicit microtask
4log করে। - দ্বিতীয় Promise reaction
5log করে। - Microtask queue খালি হয়। এরপর timer task নির্বাচিত হলে
6log করে।
Output মুখস্থ করবেন না; queue পুনর্গঠন করুন। Console statement-এর বদলে real API call থাকলেও এই পদ্ধতিই কাজ করবে।
async ও await আসলে কী schedule করে
একটি async function synchronousভাবেই শুরু হয়। await-এ পৌঁছালে function-টি suspend হয়, caller-এর কাছে একটি Promise ফেরে এবং function-এর বাকি অংশ পরে continue করার ব্যবস্থা হয়। Await করা value ready হলে continuation Promise Job mechanism দিয়ে চলে—browser-এ যেটি microtask scheduling হিসেবে দেখা যায়।
async function load() {
console.log("inside: before await");
await null;
console.log("inside: after await");
}
console.log("outside: start");
load();
console.log("outside: end");Output:
outside: start
inside: before await
outside: end
inside: after awaitএমনকি await null-ও একটি asynchronous boundary তৈরি করে। await-এর আগের code current stack-এ চলে; পরের অংশ defer হয়।
fetch ব্যবহার করলে browser JavaScript-কে Call Stack-এ ধরে না রেখেই network-এর কাজ করতে পারে। fetch() সঙ্গে সঙ্গে একটি Promise দেয়। Host পরে Promise settle করলে সংশ্লিষ্ট reaction microtask হয়। কিন্তু এতে CPU-heavy JavaScript parallel হয়ে যায় না—await-এর আগে বা পরে বড় calculation একই JavaScript thread দখল করে।
এখান থেকে একটি গুরুত্বপূর্ণ নিয়ম পাই:
await Promise.resolve()microtask-কে সুযোগ দেয়; rendering বা user input-কে সুযোগ দেওয়ার নিশ্চয়তা দেয় না।
Browser-কে অন্য task handle এবং সম্ভাব্য paint করার সুযোগ দেওয়া যদি লক্ষ্য হয়, বারবার already-resolved Promise await করা সঠিক tool নয়।
Rendering queue-এর ভেতরে নয়, তার পাশে
Browser-এর main thread শুধু JavaScript execute করে না। Style calculation, layout, paint preparation এবং compositing ও input dispatch-এর কিছু অংশও এখানে হয়।
একটি কার্যকর simplified order:
task -> microtask checkpoint -> সম্ভাব্য rendering update -> next taskএখানে সম্ভাব্য শব্দটি গুরুত্বপূর্ণ। Browser-কে প্রতিটি task-এর পরে paint করতেই হবে, এমন নয়। Display-এর frame deadline থাকে, document hidden থাকতে পারে, অথবা নতুন করে আঁকার মতো কিছু নাও থাকতে পারে।
দীর্ঘ function চলার সময় DOM change দেখা যায় না কেন
status.textContent = "Working…";
const start = performance.now();
while (performance.now() - start < 2000) {
// Expensive synchronous work-এর simulation।
}
status.textContent = "Done";User হয়তো “Working…” একবারও দেখবেন না। Script দুই সেকেন্ড current task ধরে রাখে, আবার text বদলায়, তারপর control ফেরত দেয়। দুই assignment-এর মাঝখানে browser render করার সুযোগই পায়নি।
requestAnimationFrame কোথায় বসে
requestAnimationFrame(callback) browser-কে ভবিষ্যৎ repaint-এর আগে callback চালাতে বলে। এটি সাধারণ timer-এর মতো নয়; rendering-এর সঙ্গে coordinate করা হয়। Callback one-shot, আর hidden document-এ browser এটি pause করতে পারে। Display refresh rate বদলালেও animation ঠিক রাখতে callback-এর timestamp ব্যবহার করুন। বিস্তারিত MDN-এর requestAnimationFrame() reference-এ আছে।
Loading state দেখিয়ে তারপর কাজ করার পরিচিত pattern:
status.textContent = "Working…";
requestAnimationFrame(() => {
requestAnimationFrame(() => {
doExpensiveWork();
status.textContent = "Done";
});
});Double callback প্রথম visual change paint করার opportunity দিতে পারে। তবে production code-এ পরের frame block না করে expensive work ভাগ করা বা main thread-এর বাইরে নেওয়াই সাধারণত ভালো সমাধান।
Starvation: priority যখন bug হয়ে যায়
Browser microtask queue খালি না হওয়া পর্যন্ত drain করে। তাই অসীম microtask chain timer, input ও rendering—সবকিছুকে আটকে দিতে পারে।
function starve() {
queueMicrotask(starve);
}
starve();
setTimeout(() => {
console.log("This may never run");
}, 0);প্রতিটি microtask checkpoint শেষ হওয়ার আগেই আরেকটি যোগ করছে। Stack বারবার খালি হলেও microtask queue কখনো খালি হচ্ছে না।
Application code যখন recursive Promise chain দিয়ে বিশাল collection process করে, একই সমস্যা কম স্পষ্টভাবে দেখা দিতে পারে। “Asynchronous” syntax fairness নিশ্চিত করে না। প্রতিটি continuation microtask queue-তেই থাকলে browser rendering বা next task-এ পৌঁছাতে পারে না।
Task-ও খুব বেশি সময় ধরে চললে starvation ঘটায়। web.dev main thread-এ 50 ms-এর বেশি কাজকে long task হিসেবে চিহ্নিত করে। এই সময় interaction handler অপেক্ষা করে; ফলে Interaction to Next Paint (INP) খারাপ হতে পারে এবং interface frozen মনে হয়।
Main thread responsive রাখার উপায়
Event Loop cooperative। অন্য কাজকে সুযোগ দেওয়ার জন্য আপনার code-কে যথেষ্ট ঘন ঘন control ফেরত দিতে হবে।
১. কাজের পরিমাণ কমান
Repeated calculation, অপ্রয়োজনীয় DOM measurement, অতি বড় client-side transformation এবং অতিরিক্ত third-party JavaScript বাদ দিন। যে task চালাতেই হয় না, সেটিই সবচেয়ে দ্রুত।
২. CPU work ছোট bounded chunk-এ ভাগ করুন
function processInChunks(items, chunkSize = 500) {
let index = 0;
function runChunk() {
const end = Math.min(index + chunkSize, items.length);
while (index < end) {
processItem(items[index]);
index += 1;
}
if (index < items.length) {
setTimeout(runChunk, 0);
}
}
runChunk();
}প্রতিটি timer নতুন task শুরু করে, ফলে chunk-এর মাঝে input ও rendering opportunity আসতে পারে। প্রতিটি item-এর cost সমান না হলে item count-এর বদলে elapsed time ধরে chunk সীমিত করা ভালো।
আধুনিক browser-এ prioritized continuation-সহ control ছাড়ার জন্য purpose-built scheduler.yield() আছে। সব browser-এ support না থাকায় feature detection ও fallback দরকার; web.dev-এর long task optimization guide-এ pattern দেওয়া আছে।
৩. Heavy computation Web Worker-এ নিন
Chunking responsiveness বাড়ায়, কিন্তু মোট CPU work কমায় না। Large dataset parsing, image processing, simulation বা অন্য independent computation-এর জন্য Web Worker আলাদা agent ও নিজস্ব Event Loop-এ JavaScript চালাতে পারে। Worker সরাসরি DOM পরিবর্তন করতে পারে না, তাই message-এর মাধ্যমে data আদান-প্রদান করুন এবং UI update main thread-এ রাখুন।
৪. কাজ অনুযায়ী scheduling tool বাছুন
| লক্ষ্য | উপযুক্ত tool |
|---|---|
| Current stack-এর পরে, next task-এর আগে চালানো | queueMicrotask() বা Promise reaction |
| নির্দিষ্ট delay-এর আগে না চালানো | setTimeout() |
| Repaint-এর সঙ্গে visual update coordinate করা | requestAnimationFrame() |
| Long work-এর মাঝে input/rendering-কে সুযোগ দেওয়া | fallback-সহ scheduler.yield(), অথবা task-based chunking |
| CPU-heavy work main thread-এর বাইরে নেওয়া | Web Worker |
Microtask ছোট consistency work-এর জন্য; expensive processing ভাগ করার জন্য নয়।
Browser Event Loop বনাম Node.js Event Loop
ভাষা একই, কিন্তু host আলাদা। Browser scheduling task, microtask, DOM event ও rendering-কেন্দ্রিক। Node.js libuv ব্যবহার করে এবং Event Loop-এর কাজকে কয়েকটি phase-এ ভাগ করে।
Node.js-এর simplified phase sequence:
timers
-> pending callbacks
-> poll
-> check
-> close callbacks
-> repeatNode.js-এর গুরুত্বপূর্ণ কয়েকটি পার্থক্য:
- I/O callback প্রধানত poll phase-এর আশেপাশে handle হয়।
setImmediate()callback check phase-এ চলে।- Timer-এর minimum threshold পার হওয়ার পর এবং loop উপযুক্ত phase-এ পৌঁছালে callback চলে; threshold কোনো exact deadline নয়।
process.nextTick()একটি special queue ব্যবহার করে, যা Event Loop এগোনোর আগে process হয়। Recursive ব্যবহার I/O-কে starve করতে পারে।- Promise reaction ও
queueMicrotask()microtask mechanism ব্যবহার করে, কিন্তুprocess.nextTick()-এর Node-specific ordering semantics আছে। - Node.js 20-এ ব্যবহৃত libuv 1.45.0 থেকে প্রতিটি loop iteration-এ timer poll phase-এর পরে চলে; এই পরিবর্তন
setImmediate()-এর সঙ্গে ordering-এ প্রভাব ফেলতে পারে।
Official Node.js Event Loop guide-এ phase ও version-sensitive behavior বিস্তারিত আছে। Browser-এর mnemonic Node.js-এ অন্ধভাবে প্রয়োগ করবেন না। setTimeout(..., 0) ও setImmediate()-এর মধ্যে সব context-এ একই universal order আছে—এমনটিও ধরে নেবেন না।
প্রচলিত ভুল ধারণা
“JavaScript asynchronous”
JavaScript execution সাধারণত synchronous ও run-to-completion। Host asynchronous capability দেয় এবং operation এগোলে JavaScript callback schedule করে।
“Zero-millisecond timer সঙ্গে সঙ্গে চলে”
Zero একটি minimum-delay request; current code interrupt করার অনুমতি নয়। Callback future task হয়।
“Promise parallel-এ চলে”
Promise handler schedule হয়; এটি নতুন parallel JavaScript thread তৈরি করে না। Underlying operation অন্য কোথাও হতে পারে, কিন্তু তার JavaScript reaction উপযুক্ত agent-এর stack-এই চলে।
“Microtask সব সময় সবার আগে চলে”
Current stack-এর synchronous code আগে চলে। Runtime checkpoint-এ পৌঁছালে—সাধারণত current JavaScript unwind হওয়ার পরে—microtask চলে। এটি current task-এর আগে নয়, next task-এর আগে।
“Event Loop একটি FIFO queue”
Browser-এ বিভিন্ন task source-এর সঙ্গে যুক্ত একাধিক task queue থাকতে পারে। Ordering-এর নিয়ম আছে, কিন্তু user agent-এর scheduling freedom-ও আছে। Single queue কেবল learning model।
“await CPU-heavy code-কে non-blocking করে”
await অপেক্ষার সময় async function suspend করে। তার আগে বা continuation-এর পরে থাকা expensive synchronous অংশ তখনও executing JavaScript thread block করে।
Execution order বের করার নির্ভরযোগ্য পদ্ধতি
Interview question বা production bug-এ timer, Promise ও event handler একসঙ্গে থাকলে এই process অনুসরণ করুন:
- Current task চিহ্নিত করুন।
- Call Stack-এর order ধরে synchronous statement execute করুন।
- প্রতিটি নতুন microtask exact order-এ লিখে রাখুন।
- Future task আলাদা রাখুন; timer delay যে threshold, তা মনে রাখুন।
- Stack খালি হলে microtask সম্পূর্ণ drain করুন—microtask থেকে তৈরি নতুন microtask-সহ।
- Rendering opportunity আছে কি না বিবেচনা করুন।
- Next runnable task নিয়ে একই process আবার করুন।
- Runtime Node.js হলে phase ও
process.nextTick()হিসাব করুন।
প্রয়োজনে কাগজে queue লিখুন। Task আর microtask আলাদাভাবে track করলে অধিকাংশ “trick” question আর trick থাকে না।
Real application-এ Event Loop problem debug করা
ছোট example-এ console output যথেষ্ট, কিন্তু production timing bug-এর জন্য আরও ভালো evidence দরকার।
- Browser Performance panel দিয়ে long task, event handler, timer callback, layout, paint ও frame gap record করুন।
- Application-specific timing boundary যোগ করতে
performance.mark()ওperformance.measure()ব্যবহার করুন। - Red-marked long task এবং ঘন Promise callback chain খুঁজুন।
- CPU throttling দিয়ে test করুন; desktop-এ harmless কাজ mobile-এ বড় delay হতে পারে।
- যে code-কে “yield” ভাবছেন, সেটি শুধু আরেকটি microtask schedule করছে কি না পরীক্ষা করুন।
- Node.js-এ callback server progress block করলে inspector, flame graph এবং event-loop delay/utilization metric দেখুন।
যে host-এ deploy করবেন, সেই host-এই debug করুন। Browser, Node.js, Worker ও test runner ভিন্ন scheduling behavior দেখাতে পারে।
প্রায় জিজ্ঞাসিত প্রশ্ন
এক বাক্যে JavaScript Event Loop কী?
এটি host-এর scheduling mechanism, যা current stack খালি হলে queued JavaScript চালায় এবং asynchronous operation ও browser-এর ক্ষেত্রে rendering-এর সঙ্গে কাজ সমন্বয় করে।
Microtask ও Macrotask-এর পার্থক্য কী?
Current JavaScript শেষ হওয়ার পর checkpoint-এ এবং next task-এর আগে microtask চলে। Macrotask—আরও সঠিকভাবে HTML task—timer callback বা event dispatch-এর মতো আলাদাভাবে scheduled unit।
Promise callback কি সব সময় setTimeout callback-এর আগে চলে?
একই current task থেকে দুটিই eligible হলে Promise reaction microtask checkpoint-এ future timer task-এর আগে চলে। ভিন্ন turn, environment বা বাড়তি scheduling থাকলে তুলনাটি আরও জটিল হতে পারে।
await কি Event Loop block করে?
await-এ অপেক্ষা করার সময় block করে না; async function suspend হয়। তবে suspension-এর আগে বা continuation-এর পরে synchronous কাজ block করতে পারে।
Microtask কি page freeze করতে পারে?
হ্যাঁ। একটি microtask যদি সব সময় আরেকটি microtask queue করে, checkpoint শেষ হয় না; rendering, input ও task starve করে।
Browser Event Loop ও Node.js Event Loop কি একই?
না। দুটিই JavaScript Job-কে host scheduling-এর সঙ্গে যুক্ত করে, কিন্তু browser task source ও rendering coordinate করে; Node.js libuv phase এবং process.nextTick()-এর মতো Node-specific queue ব্যবহার করে।
শেষ mental model
প্রতিটি অংশকে একটি নির্দিষ্ট দায়িত্ব দিলে Event Loop বোঝা সহজ হয়:
- Call Stack বলে: এখন কোন JavaScript execute হচ্ছে?
- একটি task host-scheduled নতুন unit of work শুরু করে।
- একটি microtask next task-এর আগে ছোট follow-up কাজ শেষ করে।
- JavaScript সুযোগ দিলে তবেই browser render করতে পারে।
- Event Loop এই transition-গুলো সমন্বয় করে।
- Node.js একই JavaScript language-কে ভিন্ন scheduling host-এর মধ্যে চালায়।
সবচেয়ে গুরুত্বপূর্ণ নিয়ম “Promise timer-কে হারায়” নয়। নিয়মটি হলো:
Current JavaScript run-to-completion শেষ করে, তারপর microtask queue drain হয়, তারপর host rendering ও অন্য task-এর দিকে এগোতে পারে।
এই sequence পরিষ্কার হলে Promise, async/await, timer, event এবং UI responsiveness আর বিচ্ছিন্ন feature মনে হবে না। এগুলো একই runtime-এ কাজ রাখার ভিন্ন ভিন্ন উপায়—যে runtime-এর scheduling rule আপনি inspect করতে, অনুমান করতে এবং মাথায় রেখে software design করতে পারেন।

আলোচনা
সাইন ইন করে আলোচনায় যোগ দিন।