Async Hooks در Node.js: راهنمای کامل

ماژول node:async_hooks در Node.js ابزاری برای ردیابی منابع ناهمگام است. با این حال، این API Experimental محسوب می‌شود و استفاده از آن توصیه نمی‌گردد، زیرا مشکلاتی در کارایی، ایمنی و قابلیت استفاده دارد. برای نیازهای ردیابی کانتکست، بهتر است از AsyncLocalStorage استفاده شود. با این وجود، آشنایی با Async Hooks برای درک چرخهٔ عمر منابع ناهمگام مفید است.

async_hooksexecutionAsyncIdtriggerAsyncIdcreateHookAsyncHook Classinit / before / after / destroy / promiseResolve

~5 دقیقه مطالعه · آخرین به‌روزرسانی ۵ دی ۱۴۰۴

1. مقدمه


Async Hooks امکان ردیابی منابع ناهمگام مانند Promiseها، Timeoutها، TCP connectionها و غیره را فراهم می‌کند. هر منبع ناهمگام دارای یک asyncId یکتا است که چرخهٔ عمر آن را مشخص می‌کند.

2. API اصلی


const async_hooks = require('node:async_hooks');

// شناسهٔ کانتکست جاری
const eid = async_hooks.executionAsyncId();

// شناسهٔ منبعی که باعث ایجاد منبع جدید شده
const tid = async_hooks.triggerAsyncId();

// ساخت یک hook جدید
const hook = async_hooks.createHook({ init, before, after, destroy, promiseResolve });
hook.enable();
hook.disable();

3. Callbackهای Hook


  • init(asyncId, type, triggerAsyncId, resource): هنگام ساخت منبع ناهمگام فراخوانی می‌شود.
  • before(asyncId): درست قبل از اجرای callback منبع.
  • after(asyncId): درست بعد از اجرای callback منبع.
  • destroy(asyncId): هنگام تخریب منبع.
  • promiseResolve(asyncId): هنگام resolve شدن Promise.

4. مدیریت خطا


اگر یکی از callbackها خطا پرتاب کند، فرآیند Node.js خاتمه می‌یابد. این رفتار برای جلوگیری از وضعیت‌های ناایمن در چرخهٔ عمر منابع طراحی شده است.

5. نکات Debugging


چون console.log() خود یک عملیات ناهمگام است، استفاده از آن داخل callbackهای AsyncHook باعث recursion بی‌نهایت می‌شود. برای Debug بهتر است از fs.writeFileSync() استفاده شود.

const fs = require('node:fs');
const util = require('node:util');

function debug(...args) {
  fs.writeFileSync('log.out', `${util.format(...args)}\n`, { flag: 'a' });
}

6. کلاس AsyncHook


کلاس AsyncHook رابطی برای مدیریت چرخهٔ عمر منابع ناهمگام فراهم می‌کند. متدهای enable() و disable() برای فعال/غیرفعال کردن hookها استفاده می‌شوند.

7. مثال عملی


نمونه‌ای از استفاده برای ردیابی رویدادهای TCP Server:

const async_hooks = require('node:async_hooks');
const fs = require('node:fs');
const net = require('node:net');
const { fd } = process.stdout;

let indent = 0;
async_hooks.createHook({
  init(asyncId, type, triggerAsyncId) {
    const eid = async_hooks.executionAsyncId();
    const indentStr = ' '.repeat(indent);
    fs.writeSync(fd, `${indentStr}${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`);
  },
  before(asyncId) {
    fs.writeSync(fd, `${' '.repeat(indent)}before: ${asyncId}\n`);
    indent += 2;
  },
  after(asyncId) {
    indent -= 2;
    fs.writeSync(fd, `${' '.repeat(indent)}after: ${asyncId}\n`);
  },
  destroy(asyncId) {
    fs.writeSync(fd, `${' '.repeat(indent)}destroy: ${asyncId}\n`);
  },
}).enable();

net.createServer(() => {}).listen(8080);

8. تفاوت executionAsyncId و triggerAsyncId


  • executionAsyncId(): نشان می‌دهد منبع در چه کانتکستی ایجاد شده.
  • triggerAsyncId: نشان می‌دهد کدام منبع باعث ایجاد منبع جدید شده.

نتیجه‌گیری


هرچند استفاده از async_hooks توصیه نمی‌شود، اما درک آن برای شناخت چرخهٔ عمر منابع ناهمگام در Node.js اهمیت دارد. برای ردیابی کانتکست بهتر است از AsyncLocalStorage استفاده شود که پایدارتر و ایمن‌تر است.

1. before(asyncId)


قبل از اجرای callback منبع ناهمگام فراخوانی می‌شود. asyncId شناسهٔ یکتای منبع است. این hook ممکن است 0 تا N بار فراخوانی شود.

  • برای منابع پایدار مانند TCP Server چندین بار اجرا می‌شود.
  • برای عملیات‌هایی مثل fs.open() فقط یک بار اجرا می‌شود.

2. after(asyncId)


بلافاصله پس از پایان اجرای callback فراخوانی می‌شود. حتی اگر خطای uncaught exception رخ دهد، بعد از آن اجرا خواهد شد.

3. destroy(asyncId)


هنگام تخریب منبع فراخوانی می‌شود. اگر منبع به GC وابسته باشد، ممکن است هرگز اجرا نشود و باعث memory leak شود. استفاده از destroy هزینهٔ اضافی دارد زیرا Promiseها را از طریق GC ردیابی می‌کند.

4. promiseResolve(asyncId)


هنگام فراخوانی resolve() در Promise اجرا می‌شود. این hook نشان‌دهندهٔ resolve شدن Promise است، حتی اگر هنوز fulfilled یا rejected نشده باشد.

new Promise((resolve) => resolve(true)).then(() => {});
// Calls: init, promiseResolve, before, after

5. executionAsyncResource()


منبع جاری را برمی‌گرداند. معمولاً یک handle داخلی Node.js است. استفاده از ویژگی‌های آن خطرناک است و ممکن است باعث crash شود.

مثال:

const { open } = require('node:fs');
const { executionAsyncId, executionAsyncResource } = require('node:async_hooks');

console.log(executionAsyncId(), executionAsyncResource()); // 1 {}
open(__filename, 'r', (err, fd) => {
  console.log(executionAsyncId(), executionAsyncResource()); // 7 FSReqWrap
});

6. executionAsyncId()


شناسهٔ کانتکست جاری را برمی‌گرداند. این شناسه مربوط به زمان اجراست، نه علت ایجاد منبع.

const async_hooks = require('node:async_hooks');
console.log(async_hooks.executionAsyncId()); // 1 bootstrap

7. triggerAsyncId()


شناسهٔ منبعی را برمی‌گرداند که باعث اجرای callback جاری شده است.

const server = net.createServer((conn) => {
  async_hooks.triggerAsyncId(); // ID of connection
}).listen(port);

8. asyncWrapProviders


از نسخهٔ v17.2.0 اضافه شده است. یک map از انواع providerها و شناسهٔ عددی آن‌ها برمی‌گرداند. جایگزین process.binding('async_wrap').Providers است.

9. Promise Execution Tracking


به‌طور پیش‌فرض Promiseها شناسهٔ asyncId دریافت نمی‌کنند. با فعال‌سازی hookها می‌توان آن‌ها را ردیابی کرد.

const { createHook, executionAsyncId, triggerAsyncId } = require('node:async_hooks');

createHook({ init() {} }).enable();
Promise.resolve(1729).then(() => {
  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});

در این مثال، Promise اول asyncId=6 و Promise دوم asyncId=7 دارد.

نتیجه‌گیری


Hookهای before، after، destroy و promiseResolve امکان ردیابی دقیق چرخهٔ عمر منابع ناهمگام را فراهم می‌کنند. متدهای executionAsyncResource، executionAsyncId و triggerAsyncId برای شناسایی کانتکست‌های جاری و منابع محرک حیاتی هستند. هرچند استفاده از async_hooks در تولید توصیه نمی‌شود، اما برای درک عمیق رفتار ناهمگام Node.js بسیار ارزشمند است.

نوشته و پژوهش‌شده توسط دکتر شاهین صیامی

مقالات مرتبط

آشنایی کامل با ماژول Cluster در Node.js و نحوه افزایش مقیاس‌پذیری سرور

ماژول Cluster در Node.js به توسعه‌دهندگان اجازه می‌دهد چندین پردازش همزمان ایجاد کنند که یک پورت سرور را به اشتراک می‌گذارند. این کار باعث استفاده بهتر از هسته‌های CPU و افزایش توان پردازش برنامه می‌شود. در این مقاله نحوه کار Cluster، مدیریت Workerها، تنظیمات مهم و مثال عملی را بررسی می‌کنیم.

ادامه

راهنمای جامع ماژول VM در Node.js (ماژول node:vm)

ماژول node:vm امکان کامپایل و اجرای کد جاوااسکریپت در کانتکست‌های ایزولهٔ V8 را فراهم می‌کند. این کانتکست‌ها مانند یک «سندباکس سبک» داخل برنامهٔ Node.js عمل می‌کنند و هرکدام globalThis مخصوص خود را دارند. این ماژول برای اجرای پویا، موتورهای قالب (Template Engines)، سیستم‌های افزونه (Plugin Systems)، REPLها و تست ایزولهٔ منطق بسیار مناسب است. اما نکتهٔ بسیار مهم: vm یک ابزار امنیتی نیست و نباید برای اجرای کدهای غیرقابل‌اعتماد استفاده شود.

ادامه

راهنمای جامع ماژول V8 در Node.js (ماژول node:v8)

راهنمای جامع ماژول V8 در Node.js (ماژول node:v8)

ادامه

راهنمای جامع Worker Threads در Node.js (ماژول node:worker_threads)

ماژول node:worker_threads امکان اجرای واقعی چندریسمانی (Multithreading) را در Node.js فراهم می‌کند. برخلاف مدل تک‌ریسمانی سنتی Node.js، Worker Threads اجازه می‌دهند کارهای سنگین CPU در ریسه‌های جداگانه اجرا شوند بدون اینکه حلقهٔ رویداد (Event Loop) مسدود شود. این ماژول از حافظهٔ اشتراکی، انتقال بدون کپی (Zero‑Copy)، کانال‌های پیام، Worker Pool، محدودیت منابع و ابزارهای همگام‌سازی پیشرفته پشتیبانی می‌کند. Worker Threads برای پردازش‌های محاسباتی سنگین ایده‌آل هستند، در حالی که I/O همچنان باید توسط ریسهٔ اصلی مدیریت شود.

ادامه

راهنمای جامع ماژول util در Node.js

ماژول node:util مجموعه‌ای از توابع کمکی قدرتمند برای استفاده در هستهٔ Node.js و همچنین توسعهٔ برنامه‌ها ارائه می‌دهد. این ماژول ابزارهایی برای دیباگ، بازرسی اشیا، تبدیل Callback ↔ Promise، قالب‌بندی رشته‌ها، بررسی انواع، مدیریت خطاها، رمزگذاری متن، پردازش MIME، پارس آرگومان‌های CLI، سیگنال‌های Abort، و ابزارهای متفرقه فراهم می‌کند. این ماژول یکی از جعبه‌ابزارهای اصلی و ضروری برای توسعه‌دهندگان Node.js است.

ادامه

راهنمای جامع ماژول URL در Node.js

ماژول node:url ابزارهایی برای تحلیل، ساخت، و دست‌کاری URLها در Node.js فراهم می‌کند. Node.js دو API برای کار با URL دارد: WHATWG URL API — استاندارد مدرن و سازگار با مرورگرها (توصیه‌شده). Legacy API — API قدیمی Node.js (غیرتوصیه‌شده). WHATWG URL API یک رابط تمیز، سازگار و قدرتمند برای کار با اجزای URL، پارامترهای query، الگوهای URL و حتی Blob URLها ارائه می‌دهد.

ادامه