async/await و حلقه‌ی رویداد در جاوااسکریپت؛ چرا ترتیب اجرا گاهی عجیب است
آموزش

async/await و حلقه‌ی رویداد در جاوااسکریپت؛ چرا ترتیب اجرا گاهی عجیب است

کد همزمان همیشه اول اجرا می‌شود، بعد Promiseها، و در آخر setTimeout؛ حتی با تأخیر صفر. این ترتیب شانسی نیست

تحریریه‌ی دینا کد
نویسنده
۴ دقیقه مطالعه۵ بازدید
اشتراک گذاری۵
فهرست مطالب
  1. ۱یک مثال که ترتیبش عجیب به نظر می‌رسد
  2. ۲async فقط یک Promise برمی‌گرداند
  3. ۳await فقط همان تابع را متوقف می‌کند، نه کل برنامه را
  4. ۴گیر رایج: await داخل forEach
  5. ۵دو راه درست: یکی‌یکی یا همزمان

کد همزمان (sync) همیشه اول اجرا می‌شود، بعد microtaskها (نتیجه‌ی Promiseها)، و در آخر macrotaskها (setTimeout, setInterval). این ترتیب نه شانسی است و نه به ترتیب نوشتن کد بستگی دارد.

یک مثال که ترتیبش عجیب به نظر می‌رسد

jsconsole.log('1: شروع');

setTimeout(() => console.log('4: setTimeout'), 0);

Promise.resolve().then(() => console.log('3: promise.then'));

console.log('2: پایان کد همزمان');
1: شروع
2: پایان کد همزمان
3: promise.then
4: setTimeout

با اینکه setTimeout با تأخیر ۰ نوشته شده و قبل از Promise.resolve() هم آمده، بعد از آن اجرا می‌شود. جاوااسکریپت اول تمام کد همزمان را تا انتها اجرا می‌کند (خط ۱ و ۲)، بعد صف microtask را خالی می‌کند (.then)، و فقط در آخر سراغ صف macrotask می‌رود (setTimeout)، حتی با تأخیر صفر.

async فقط یک Promise برمی‌گرداند

jsasync function f() { return 5; }
const r = f();
console.log(r); // Promise { 5 }
r.then(v => console.log('resolved:', v));
console.log('این خط قبل از resolve شدن پرینت می‌شود');
Promise { 5 }
این خط قبل از resolve شدن پرینت می‌شود
resolved: 5

هر async function، مهم نیست چه چیزی return کند، همیشه یک Promise برمی‌گرداند. حتی return 5 هم Promise { 5 } می‌شود، نه خودِ عدد ۵.

await فقط همان تابع را متوقف می‌کند، نه کل برنامه را

jsasync function work() {
  console.log('A: شروع کار async');
  await new Promise(res => setTimeout(res, 50));
  console.log('C: بعد از await');
}
work();
console.log('B: این وسط اجرا می‌شود، بدون توقف');
A: شروع کار async
B: این وسط اجرا می‌شود، بدون توقف
C: بعد از await

await اجرای تابع work را نگه می‌دارد، ولی بقیه‌ی برنامه (خط B) منتظرش نمی‌ماند و بلافاصله اجرا می‌شود. این دقیقاً همان چیزی است که جاوااسکریپت را non-blocking نگه می‌دارد.

گیر رایج: await داخل forEach

jsconst delay = (ms, label) => new Promise(res => setTimeout(() => {
  console.log('انجام شد: ' + label);
  res(ms);
}, ms));

[300, 100, 200].forEach(async (ms) => {
  await delay(ms, ms);
});
console.log('این خط بلافاصله پرینت می‌شود، بدون صبر کردن');
این خط بلافاصله پرینت می‌شود، بدون صبر کردن
انجام شد: 100
انجام شد: 200
انجام شد: 300

forEach منتظر Promiseهایی که callbackاش برمی‌گرداند نمی‌ماند، چون خروجی callback را اصلاً دور می‌ریزد. اگر واقعاً به «صبر کردن تا همه تمام شوند» نیاز داری، باید از for...of یا Promise.all استفاده کنی.

دو راه درست: یکی‌یکی یا همزمان

js// یکی‌یکی؛ هرکدام منتظر قبلی می‌ماند
for (const ms of [300, 100, 200]) {
  await delay(ms, ms);
}
// انجام شد: 300, 100, 200 (به همین ترتیب)
js// همزمان؛ همه با هم شروع می‌شوند، ولی کد صبر می‌کند تا همه تمام شوند
const results = await Promise.all([300, 100, 200].map(ms => delay(ms, ms)));
console.log(results); // [300, 100, 200]

نکته‌ی ظریف اینجاست که با Promise.all، هرکدام زودتر تمام شود، زودتر لاگ می‌شود (۱۰۰ اول تمام می‌شود)، ولی آرایه‌ی نتیجه همیشه به ترتیب همان آرایه‌ی ورودی است (۳۰۰، ۱۰۰، ۲۰۰)، نه به ترتیب اتمام.

اگر می‌خواهی جاوااسکریپت را از پایه و با تمرین واقعی یاد بگیری، از مقدارهای اولیه تا همین حلقه‌ی رویداد، دوره‌ی جاوااسکریپت از صفر دقیقاً همین رفتارها را قدم‌به‌قدم تمرین می‌دهد.

تحریریه‌ی دینا کد
نویسنده

نظرها

اولین نفری باشید که نظر می‌دهد
برای ثبت نظر وارد شوید

سؤالتان را بپرسید یا تجربه‌تان را بنویسید تا نویسنده‌ی مطلب پاسخ بدهد.

مطالب مرتبط

از همین دسته
مدیریت خطا با try و except در پایتون
آموزش

مدیریت خطا با try و except در پایتون

راهنمای گام‌به‌گام مهار خطاهای زمان اجرا در پایتون با بلوک‌های try و except همراه با بررسی خطاهای رایج و ساختار اصولی.

تحریریه‌ی دینا کد۵ دقیقه
مدیریت خطا در Rust با Result و Option
آموزش

مدیریت خطا در Rust با Result و Option

زبان Rust به جای ساختارهای سنتی استثنا از نوع‌های Result و Option برای مدیریت شفاف و پیش‌بینی‌پذیر خطاها در زمان کامپایل استفاده می‌کند.

تحریریه‌ی دینا کد۶ دقیقه
تفاوت واحدهای px و em و rem در CSS و کاربرد هرکدام
آموزش

تفاوت واحدهای px و em و rem در CSS و کاربرد هرکدام

واحدهای px، em و rem هرکدام برای بخش مشخصی از صفحه‌آرایی وب طراحی شده‌اند و انتخاب نادرست آن‌ها مقیاس‌پذیری و دسترسی‌پذیری سایت را مختل می‌کند.

تحریریه‌ی دینا کد۶ دقیقه