Skip to content

setTimeout(fn, 0) সাথে সাথে চলে না কেন

ম্যাক্রোটাস্ক, মাইক্রোটাস্ক আর রেন্ডারিং — ইভেন্ট লুপের ধাপগুলো জানলে অ্যাসিঙ্ক কোডের ক্রম আর অনুমান করতে হয় না।

নাফিস রহমান

নাফিস রহমান

ডিজাইন সিস্টেম লিড

৭ মিনিটের পড়া
শেয়ার

এই কোডটার আউটপুট কী হবে?

console.log('১')

setTimeout(() => console.log('২'), 0)

Promise.resolve().then(() => console.log('৩'))

console.log('৪')

উত্তর ১ ৪ ৩ ২setTimeout-এ ০ মিলিসেকেন্ড দেওয়া সত্ত্বেও সেটা সবার শেষে চলে, এমনকি এমন একটা প্রমিসের পরেও যেটা তার পরে লেখা হয়েছে।

"০ মানে এখনই নয়" — এইটুকু মনে রাখলে কাজ চলে যায়, কিন্তু কেন নয় সেটা জানলে আর মুখস্থ করতে হয় না।

কল স্ট্যাক আগে শেষ হয়

জাভাস্ক্রিপ্ট সিঙ্গল-থ্রেডেড। যেকোনো মুহূর্তে একটাই জিনিস চলে, আর সেটা কল স্ট্যাকের উপরের ফাংশনটা।

ইভেন্ট লুপ কোনো কিছু তুলে নেওয়ার আগে স্ট্যাক পুরো খালি হওয়া লাগে। তাই উপরের কোডে console.log('১') আর console.log('৪') — যেগুলো সিঙ্ক্রোনাস — আগে চলবেই। বাকি সবকিছু সারিতে অপেক্ষা করে।

এখান থেকেই সবচেয়ে সাধারণ বাগটা আসে:

// ব্রাউজার এই পুরো লুপটা শেষ না হওয়া পর্যন্ত জমে থাকে
for (let i = 0; i < 1e9; i++) {}

স্ট্যাক খালি না হলে ইভেন্ট লুপ কিছুই করতে পারে না — ক্লিক হ্যান্ডলার চলবে না, রেন্ডারও হবে না। "পেজ হ্যাং করে আছে" মানে বেশিরভাগ সময় "স্ট্যাক খালি হচ্ছে না"।

দুইটা আলাদা সারি

স্ট্যাক খালি হলে ইভেন্ট লুপ যা দেখে, তা একটা সারি নয় — দুইটা, আর তাদের অগ্রাধিকার আলাদা।

মাইক্রোটাস্ক সারি

  • Promise.then / .catch / .finally
  • await-এর পরের অংশ
  • queueMicrotask
  • MutationObserver

ম্যাক্রোটাস্ক সারি (স্পেকে "টাস্ক")

  • setTimeout, setInterval
  • DOM ইভেন্ট (click, input)
  • নেটওয়ার্ক কলব্যাক
  • requestAnimationFrame (আলাদা ধাপে, রেন্ডারের আগে)

নিয়মটা এক লাইনে:

প্রতিটা ম্যাক্রোটাস্কের পর, পুরো মাইক্রোটাস্ক সারি খালি করা হয় — তারপর পরের ম্যাক্রোটাস্ক।

তাই (প্রমিস, মাইক্রোটাস্ক) -এর (টাইমআউট, ম্যাক্রোটাস্ক) আগে চলে। ০ মিলিসেকেন্ড লেখা থাকলেও সেটা অন্য সারিতে, আর ওই সারির পালা পরে।

"পুরো" শব্দটা গুরুত্বপূর্ণ

মাইক্রোটাস্ক সারি খালি করার সময় নতুন মাইক্রোটাস্ক যোগ হলে সেগুলোও একই দফায় চলে। এটা সুবিধা, আবার ফাঁদও:

function loop() {
  Promise.resolve().then(loop)
}
loop() // পেজ জমে যায়

এখানে কোনো ইনফিনিট while নেই, তবু ব্রাউজার আর কিছু করতে পারে না — সারিটা কখনো খালি হয় না, তাই লুপ কখনো পরের ধাপে যায় না।

একই কোড setTimeout দিয়ে লিখলে পেজ সচল থাকে:

function loop() {
  setTimeout(loop, 0)
}
loop() // চলতেই থাকে, কিন্তু পেজ ব্যবহারযোগ্য

কারণ প্রতিটা ম্যাক্রোটাস্কের মাঝে ব্রাউজার রেন্ডার করার সুযোগ পায়।

await আসলে কী

await নতুন কিছু নয় — এটা .then-এর সিনট্যাক্স।

async function run() {
  console.log('ক')
  await null // ← এখানেই ফাংশনটা থামে
  console.log('খ') // ← এই অংশটা মাইক্রোটাস্ক
}

run()
console.log('গ')

আউটপুট ক গ খawait null — যেখানে অপেক্ষা করার মতো কিছুই নেই — তবু ফাংশনটা থামে, বাকিটা মাইক্রোটাস্ক হিসেবে সারিতে যায়, আর আগে চলে।

তাই await-এর পরের প্রতিটা লাইন কার্যত একটা কলব্যাকের ভেতরে। সিরিয়ালি লেখা যায় বলে এটা ভুলে যাওয়া সহজ।

রেন্ডারিং কোথায়

ব্রাউজার সাধারণত প্রতি ফ্রেমে একবার রেন্ডার করে — মোটামুটি ১৬.৭ms পরপর, ৬০Hz-এ। ধাপটা এরকম:

ম্যাক্রোটাস্ক → মাইক্রোটাস্ক (সব) → rAF কলব্যাক → স্টাইল → লেআউট → পেইন্ট

দুইটা ব্যবহারিক ফল:

এক — একই টিকে DOM বদলে আবার পড়লে ব্রাউজার জোর করে লেআউট হিসাব করে (layout thrashing):

// খারাপ — প্রতি ধাপে জোর করে reflow
for (const el of items) {
  el.style.height = el.offsetHeight + 10 + 'px'
}

// ভালো — আগে সব পড়ুন, তারপর সব লিখুন
const heights = items.map((el) => el.offsetHeight)
items.forEach((el, i) => (el.style.height = heights[i] + 10 + 'px'))

দুই — অ্যানিমেশন setTimeout দিয়ে নয়, requestAnimationFrame দিয়ে। rAF রেন্ডার ধাপের ঠিক আগে চলে, তাই ফ্রেম মিস হয় না।

০ আসলে ০ নয়

শেষ একটা খুঁটিনাটি: HTML স্পেক অনুযায়ী নেস্টেড setTimeout পাঁচ স্তরের পর সর্বনিম্ন ৪ মিলিসেকেন্ড ক্ল্যাম্প হয়। ব্যাকগ্রাউন্ড ট্যাবে ব্রাউজার এটা আরও বাড়িয়ে ১০০০ms-ও করে দিতে পারে।

অর্থাৎ setTimeout(fn, 0) মানে "যত দ্রুত সম্ভব" নয়, মানে "পরের টাস্ক হিসেবে, সুবিধামতো"। এখনই দরকার হলে queueMicrotask আছে — কিন্তু তাতে ব্রাউজার রেন্ডার করার সুযোগ পায় না, যেটা প্রায়ই আপনি চাইছিলেন।

  • #JavaScript
  • #ইভেন্ট লুপ
  • #অ্যাসিঙ্ক
শেয়ার

সম্পর্কিত লেখা

ডিজাইন ও টাইপোগ্রাফি৯ মিনিটের পড়া

ওয়েবে বাংলা টাইপোগ্রাফি — যে সমস্যাগুলো নিয়ে কেউ লেখে না

লাইন-হাইট, মাত্রার সংঘর্ষ, uppercase-এর অর্থহীনতা আর ফন্ট ফলব্যাক। ল্যাটিনের জন্য বানানো একটা ডিজাইন সিস্টেমে বাংলা বসাতে গিয়ে যা যা ঠিক করতে হয়েছিল।

তাসনিম আরাতাসনিম আরা
ডিজাইন সিস্টেম৮ মিনিটের পড়া

ডিজাইন সিস্টেম বানানোর আগে যে ভুলগুলো এড়ানো দরকার

বেশিরভাগ টোকেন সিস্টেম প্রথম রিব্র্যান্ডেই ভেঙে পড়ে। কারণটা প্রায় সবসময় এক — দুইটা লেয়ার আছে, দরকার ছিল তিনটা।

নাফিস রহমাননাফিস রহমান
ক্যারিয়ার৬ মিনিটের পড়া

ফ্রিল্যান্সিংয়ে রেট ঠিক করা — ঘণ্টার হিসাব থেকে বেরোনোর উপায়

ঘণ্টায় দাম দিলে আপনি দ্রুত কাজ শেখার জন্য শাস্তি পান। স্কোপ, বাফার আর কীভাবে একটা সংখ্যা বলার আগে প্রশ্নগুলো করতে হয়।

তাসনিম আরাতাসনিম আরা

One useful email a month

Design system patterns, front-end techniques and case study breakdowns. No promotions, no digest of other people's links.

Unsubscribe anytime. We never share your address.