async/await در Node.js چطور کار میکنه؟
خلاصهٔ کاملتر
هر تابعی که با async تعریف بشه، قراردادش عوض میشه: دیگه هر چی return کنه، خروجیاش یه Promiseه. چه عدد ۴۲ برگردونه، چه خطا پرتاب کنه، چه چند تا عملیات شبکه داشته باشه — فرقی نمیکنه. این موضوع یه دسته از باگهای رایج رو توضیح میده؛ مثلاً وقتی یه async callback به Array.map() میدی، یه آرایه از Promiseها تحویل میگیری، نه نتایج واقعی.
وقتی await توی تابع اجرا میشه، فقط همون تابع متوقف میشه — نه کل برنامه. V8 وضعیت فعلی اجرا (متغیرهای محلی، موقعیت bytecode) رو روی heap ذخیره میکنه، بعد کنترل رو به caller برمیگردونه. بعداً وقتی Promise مربوطه settle شد، یه PromiseReactionJob در صف microtask قرار میگیره و V8 تابع رو از همون نقطهای که متوقف شده بود ادامه میده. به همین دلیله که در این مثال خروجی 1، 2، 4، 3 میشه:
console.log("1");
async function run() {
console.log("2");
await Promise.resolve();
console.log("3");
}
run();
console.log("4");یه نکته مهم در Node.js اینه که process.nextTick() نسبت به microtaskهای Promise اولویت داره. یعنی اگه همزمان یه await و یه nextTick داشته باشی، callback مربوط به nextTick زودتر اجرا میشه. این تفاوت منبع خیلی از باگهای ترتیب اجراست.
از نظر داخلی، V8 برای هر تابع async یه آبجکت JSAsyncFunctionObject میسازه که به machinery مربوط به generator مرتبطه. این آبجکت شامل Promise بیرونی، وضعیت continuation، و closureهای resume برای حالتهای fulfillment و rejection میشه. Promise بیرونی همون ابتدای فراخوانی تابع ساخته میشه — و در تمام طول تابع، حتی اگه ۵ تا await داشته باشی، همون Promise پابرجاست.
یه جنبه عملی مهم، مصرف حافظهه. وقتی تابع روی یه await متوقف میمونه، تمام متغیرهای محلیاش زنده میمونن. اگه یه Buffer سنگین ۵۰ مگابایتی داری و بعدش await db.save() میزنی، اون Buffer تا وقتی Promise دیتابیس resolve نشده، در حافظه میمونه. مشکل اصلی نه خود آبجکت async، بلکه متغیرهاییه که نگه میداری.
مدیریت خطا در async/await با try/catch انجام میشه و بهصورت طبیعی کار میکنه؛ چون V8 تابع رو درست داخل همون ناحیه try که قبلاً بوده resume میکنه. اگه Promise ریجکت بشه، await خطا پرتاب میکنه و catch بلاک اون رو میگیره — درست مثل یه خطای synchronous معمولی.
نکات کلیدی:
- هر تابع async همیشه یه Promise برمیگردونه، بدون استثنا
- await فقط تابع جاری رو متوقف میکنه؛ بقیه کد به اجرا ادامه میده
- کد قبل از اولین await کاملاً synchronous اجرا میشه
- process.nextTick() در Node.js نسبت به promise microtaskها اولویت داره
- متغیرهای محلی تا پایان تابع زنده میمونن و میتونن حافظه رو نگه دارن
- return await داخل try/catch یا finally اهمیت داره — بدون await، خطاهای async از دست میرن
- تابعی که await نداشته باشه، body کاملاش synchronous اجرا میشه، ولی نتیجهاش هنوز از طریق Promise تحویل داده میشه




