کانتکست ناهمزمان جاوا اسکریپت را کاوش کنید، با تمرکز بر تکنیکهای مدیریت متغیرهای محدود به درخواست برای برنامههای قوی و مقیاسپذیر. با AsyncLocalStorage و کاربردهای آن آشنا شوید.
کانتکست ناهمزمان جاوا اسکریپت: تسلط بر مدیریت متغیرهای محدود به درخواست
برنامهنویسی ناهمزمان یکی از سنگ بناهای توسعه مدرن جاوا اسکریپت، به ویژه در محیطهایی مانند Node.js است. با این حال، مدیریت کانتکست و متغیرهای محدود به درخواست در طول عملیات ناهمزمان میتواند چالشبرانگیز باشد. رویکردهای سنتی اغلب منجر به کد پیچیده و خرابی احتمالی دادهها میشوند. این مقاله به بررسی قابلیتهای کانتکست ناهمزمان جاوا اسکریپت، به ویژه با تمرکز بر AsyncLocalStorage، و چگونگی سادهسازی مدیریت متغیرهای محدود به درخواست برای ساخت برنامههای قوی و مقیاسپذیر میپردازد.
درک چالشهای کانتکست ناهمزمان
در برنامهنویسی همزمان، مدیریت متغیرها در محدوده یک تابع ساده است. هر تابع کانتکست اجرایی خود را دارد و متغیرهای تعریف شده در آن کانتکست ایزوله هستند. با این حال، عملیات ناهمزمان پیچیدگیهایی را به همراه دارد زیرا به صورت خطی اجرا نمیشوند. کالبکها، پرامیسها و async/await کانتکستهای اجرایی جدیدی را معرفی میکنند که میتوانند حفظ و دسترسی به متغیرهای مربوط به یک درخواست یا عملیات خاص را دشوار سازند.
سناریویی را در نظر بگیرید که در آن نیاز به ردیابی یک شناسه درخواست منحصر به فرد در طول اجرای یک کنترلکننده درخواست دارید. بدون یک مکانیسم مناسب، ممکن است به ارسال شناسه درخواست به عنوان آرگومان به هر تابعی که در پردازش درخواست دخیل است، متوسل شوید. این رویکرد دستوپاگیر، مستعد خطا و باعث ایجاد وابستگی شدید در کد شما میشود.
مشکل انتشار کانتکست
- شلوغی کد: ارسال متغیرهای کانتکست از طریق فراخوانیهای متعدد توابع، به طور قابل توجهی پیچیدگی کد را افزایش داده و خوانایی را کاهش میدهد.
- وابستگی شدید: توابع به متغیرهای کانتکست خاصی وابسته میشوند، که باعث میشود قابلیت استفاده مجدد و تست آنها کمتر شود.
- مستعد خطا: فراموش کردن ارسال یک متغیر کانتکست یا ارسال مقدار اشتباه میتواند منجر به رفتار غیرقابل پیشبینی و مشکلاتی شود که دیباگ کردن آنها دشوار است.
- سربار نگهداری: تغییرات در متغیرهای کانتکست نیازمند اصلاحات در بخشهای متعددی از کدبیس است.
این چالشها نیاز به یک راهحل زیباتر و قویتر برای مدیریت متغیرهای محدود به درخواست در محیطهای ناهمزمان جاوا اسکریپت را برجسته میکنند.
معرفی AsyncLocalStorage: راهحلی برای کانتکست ناهمزمان
AsyncLocalStorage، که در Node.js نسخه v14.5.0 معرفی شد، مکانیزمی برای ذخیرهسازی دادهها در طول عمر یک عملیات ناهمزمان فراهم میکند. این ابزار اساساً یک کانتکست ایجاد میکند که در سراسر مرزهای ناهمزمان پایدار است و به شما امکان میدهد به متغیرهای خاص یک درخواست یا عملیات دسترسی داشته باشید و آنها را تغییر دهید بدون اینکه نیاز به ارسال صریح آنها باشد.
AsyncLocalStorage بر اساس هر کانتکست اجرایی عمل میکند. هر عملیات ناهمزمان (به عنوان مثال، یک کنترلکننده درخواست) فضای ذخیرهسازی ایزوله خود را دریافت میکند. این امر تضمین میکند که دادههای مرتبط با یک درخواست به طور تصادفی به درخواست دیگری نشت نکند و یکپارچگی و ایزولهسازی دادهها حفظ شود.
AsyncLocalStorage چگونه کار میکند
کلاس AsyncLocalStorage متدهای کلیدی زیر را ارائه میدهد:
getStore(): فروشگاه (store) فعلی مرتبط با کانتکست اجرایی فعلی را برمیگرداند. اگر فروشگاهی وجود نداشته باشد،undefinedرا برمیگرداند.run(store, callback, ...args):callbackارائهشده را در یک کانتکست ناهمزمان جدید اجرا میکند. آرگومانstoreفضای ذخیرهسازی کانتکست را مقداردهی اولیه میکند. تمام عملیات ناهمزمان که توسط کالبک فعال میشوند به این فروشگاه دسترسی خواهند داشت.enterWith(store): وارد کانتکستstoreارائهشده میشود. این متد زمانی مفید است که نیاز به تنظیم صریح کانتکست برای یک بلوک خاص از کد دارید.disable(): نمونه AsyncLocalStorage را غیرفعال میکند. دسترسی به فروشگاه پس از غیرفعالسازی منجر به خطا خواهد شد.
خود فروشگاه یک شیء ساده جاوا اسکریپت (یا هر نوع دادهای که شما انتخاب کنید) است که متغیرهای کانتکستی را که میخواهید مدیریت کنید، در خود نگه میدارد. شما میتوانید شناسههای درخواست، اطلاعات کاربر یا هر داده دیگری مرتبط با عملیات فعلی را ذخیره کنید.
مثالهای عملی از AsyncLocalStorage در عمل
بیایید استفاده از AsyncLocalStorage را با چند مثال عملی نشان دهیم.
مثال ۱: ردیابی شناسه درخواست در یک وب سرور
یک وب سرور Node.js را با استفاده از Express.js در نظر بگیرید. ما میخواهیم به طور خودکار یک شناسه درخواست منحصر به فرد برای هر درخواست ورودی ایجاد و ردیابی کنیم. این شناسه میتواند برای لاگینگ، ردیابی و دیباگ کردن استفاده شود.
const express = require('express');
const { AsyncLocalStorage } = require('async_hooks');
const { v4: uuidv4 } = require('uuid');
const app = express();
const asyncLocalStorage = new AsyncLocalStorage();
app.use((req, res, next) => {
const requestId = uuidv4();
asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage.getStore().set('requestId', requestId);
console.log(`Request received with ID: ${requestId}`);
next();
});
});
app.get('/', (req, res) => {
const requestId = asyncLocalStorage.getStore().get('requestId');
console.log(`Handling request with ID: ${requestId}`);
res.send(`Hello, Request ID: ${requestId}`);
});
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
در این مثال:
- ما یک نمونه
AsyncLocalStorageایجاد میکنیم. - ما از میانافزار Express برای رهگیری هر درخواست ورودی استفاده میکنیم.
- در داخل میانافزار، ما با استفاده از
uuidv4()یک شناسه درخواست منحصر به فرد ایجاد میکنیم. - ما متد
asyncLocalStorage.run()را برای ایجاد یک کانتکست ناهمزمان جدید فراخوانی میکنیم. ما فروشگاه را با یکMapمقداردهی اولیه میکنیم که متغیرهای کانتکست ما را در خود نگه میدارد. - در داخل کالبک
run()، ماrequestIdرا با استفاده ازasyncLocalStorage.getStore().set('requestId', requestId)در فروشگاه تنظیم میکنیم. - سپس
next()را فراخوانی میکنیم تا کنترل را به میانافزار یا کنترلکننده مسیر بعدی منتقل کنیم. - در کنترلکننده مسیر (
app.get('/'))، ماrequestIdرا با استفاده ازasyncLocalStorage.getStore().get('requestId')از فروشگاه بازیابی میکنیم.
اکنون، صرف نظر از اینکه چه تعداد عملیات ناهمزمان در کنترلکننده درخواست فعال شود، شما همیشه میتوانید با استفاده از asyncLocalStorage.getStore().get('requestId') به شناسه درخواست دسترسی داشته باشید.
مثال ۲: احراز هویت و مجوزدهی کاربر
یکی دیگر از موارد استفاده رایج، مدیریت اطلاعات احراز هویت و مجوزدهی کاربر است. فرض کنید یک میانافزار دارید که یک کاربر را احراز هویت کرده و شناسه کاربری او را بازیابی میکند. شما میتوانید شناسه کاربری را در AsyncLocalStorage ذخیره کنید تا برای میانافزارها و کنترلکنندههای مسیر بعدی در دسترس باشد.
const express = require('express');
const { AsyncLocalStorage } = require('async_hooks');
const app = express();
const asyncLocalStorage = new AsyncLocalStorage();
// میانافزار احراز هویت (مثال)
const authenticateUser = (req, res, next) => {
// شبیهسازی احراز هویت کاربر (با منطق واقعی خود جایگزین کنید)
const userId = req.headers['x-user-id'] || 'guest'; // دریافت شناسه کاربر از هدر
asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage.getStore().set('userId', userId);
console.log(`User authenticated with ID: ${userId}`);
next();
});
};
app.use(authenticateUser);
app.get('/profile', (req, res) => {
const userId = asyncLocalStorage.getStore().get('userId');
console.log(`Accessing profile for user ID: ${userId}`);
res.send(`Profile for User ID: ${userId}`);
});
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
در این مثال، میانافزار authenticateUser شناسه کاربری را (که در اینجا با خواندن یک هدر شبیهسازی شده است) بازیابی کرده و آن را در AsyncLocalStorage ذخیره میکند. سپس کنترلکننده مسیر /profile میتواند بدون نیاز به دریافت شناسه کاربری به عنوان یک پارامتر صریح، به آن دسترسی داشته باشد.
مثال ۳: مدیریت تراکنش پایگاه داده
در سناریوهایی که شامل تراکنشهای پایگاه داده هستند، میتوان از AsyncLocalStorage برای مدیریت کانتکست تراکنش استفاده کرد. شما میتوانید اتصال پایگاه داده یا شیء تراکنش را در AsyncLocalStorage ذخیره کنید، و این تضمین میکند که تمام عملیات پایگاه داده در یک درخواست خاص از همان تراکنش استفاده میکنند.
const express = require('express');
const { AsyncLocalStorage } = require('async_hooks');
const app = express();
const asyncLocalStorage = new AsyncLocalStorage();
// شبیهسازی یک اتصال پایگاه داده
const db = {
query: (sql, callback) => {
const transactionId = asyncLocalStorage.getStore()?.get('transactionId') || 'No Transaction';
console.log(`Executing SQL: ${sql} in Transaction: ${transactionId}`);
// شبیهسازی اجرای کوئری پایگاه داده
setTimeout(() => {
callback(null, { success: true });
}, 50);
},
};
// میانافزار برای شروع یک تراکنش
const startTransaction = (req, res, next) => {
const transactionId = Math.random().toString(36).substring(2, 15); // ایجاد یک شناسه تراکنش تصادفی
asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage.getStore().set('transactionId', transactionId);
console.log(`Starting transaction: ${transactionId}`);
next();
});
};
app.use(startTransaction);
app.get('/data', (req, res) => {
db.query('SELECT * FROM data', (err, result) => {
if (err) {
return res.status(500).send('Error querying data');
}
res.send('Data retrieved successfully');
});
});
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
در این مثال سادهشده:
- میانافزار
startTransactionیک شناسه تراکنش ایجاد کرده و آن را درAsyncLocalStorageذخیره میکند. - تابع شبیهسازی شده
db.queryشناسه تراکنش را از فروشگاه بازیابی کرده و آن را لاگ میکند، که نشان میدهد کانتکست تراکنش در داخل عملیات ناهمزمان پایگاه داده در دسترس است.
کاربرد پیشرفته و ملاحظات
میانافزار و انتشار کانتکست
AsyncLocalStorage به ویژه در زنجیرههای میانافزار مفید است. هر میانافزار میتواند به کانتکست مشترک دسترسی داشته باشد و آن را تغییر دهد، که به شما امکان میدهد خطوط لوله پردازش پیچیده را به راحتی بسازید.
اطمینان حاصل کنید که توابع میانافزار شما برای انتشار صحیح کانتکست طراحی شدهاند. از asyncLocalStorage.run() یا asyncLocalStorage.enterWith() برای پیچیدن عملیات ناهمزمان و حفظ جریان کانتکست استفاده کنید.
مدیریت خطا و پاکسازی
مدیریت صحیح خطا هنگام استفاده از AsyncLocalStorage حیاتی است. اطمینان حاصل کنید که استثناها را به درستی مدیریت کرده و هرگونه منبع مرتبط با کانتکست را پاکسازی میکنید. استفاده از بلوکهای try...finally را برای اطمینان از آزاد شدن منابع حتی در صورت بروز خطا در نظر بگیرید.
ملاحظات عملکردی
در حالی که AsyncLocalStorage یک راه راحت برای مدیریت کانتکست فراهم میکند، لازم است به پیامدهای عملکردی آن توجه داشته باشید. استفاده بیش از حد از AsyncLocalStorage میتواند سربار ایجاد کند، به ویژه در برنامههای با توان عملیاتی بالا. کد خود را پروفایل کنید تا تنگناهای احتمالی را شناسایی کرده و بر اساس آن بهینهسازی کنید.
از ذخیره حجم زیادی از دادهها در AsyncLocalStorage خودداری کنید. فقط متغیرهای کانتکست ضروری را ذخیره کنید. اگر نیاز به ذخیره اشیاء بزرگتر دارید، به جای خود اشیاء، ذخیره ارجاع به آنها را در نظر بگیرید.
جایگزینهای AsyncLocalStorage
در حالی که AsyncLocalStorage ابزار قدرتمندی است، رویکردهای جایگزینی برای مدیریت کانتکست ناهمزمان وجود دارد که به نیازهای خاص و فریمورک شما بستگی دارد.
- ارسال صریح کانتکست: همانطور که قبلاً ذکر شد، ارسال صریح متغیرهای کانتکست به عنوان آرگومان به توابع یک رویکرد ابتدایی، هرچند کمتر زیبا، است.
- اشیاء کانتکست: ایجاد یک شیء کانتکست اختصاصی و ارسال آن میتواند خوانایی را در مقایسه با ارسال متغیرهای جداگانه بهبود بخشد.
- راهحلهای خاص فریمورک: بسیاری از فریمورکها مکانیسمهای مدیریت کانتکست خود را ارائه میدهند. به عنوان مثال، NestJS ارائهدهندگان محدود به درخواست (request-scoped providers) را فراهم میکند.
دیدگاه جهانی و بهترین شیوهها
هنگام کار با کانتکست ناهمزمان در یک زمینه جهانی، موارد زیر را در نظر بگیرید:
- مناطق زمانی: هنگام کار با اطلاعات تاریخ و زمان در کانتکست، به مناطق زمانی توجه داشته باشید. اطلاعات منطقه زمانی را به همراه مهرهای زمانی ذخیره کنید تا از ابهام جلوگیری شود.
- محلیسازی: اگر برنامه شما از چندین زبان پشتیبانی میکند، محلی (locale) کاربر را در کانتکست ذخیره کنید تا اطمینان حاصل شود که محتوا به زبان صحیح نمایش داده میشود.
- واحد پول: اگر برنامه شما تراکنشهای مالی را مدیریت میکند، واحد پول کاربر را در کانتکست ذخیره کنید تا اطمینان حاصل شود که مبالغ به درستی نمایش داده میشوند.
- فرمتهای داده: از فرمتهای مختلف داده که در مناطق مختلف استفاده میشود، آگاه باشید. به عنوان مثال، فرمتهای تاریخ و فرمتهای اعداد میتوانند به طور قابل توجهی متفاوت باشند.
نتیجهگیری
AsyncLocalStorage یک راهحل قدرتمند و زیبا برای مدیریت متغیرهای محدود به درخواست در محیطهای ناهمزمان جاوا اسکریپت ارائه میدهد. با ایجاد یک کانتکست پایدار در سراسر مرزهای ناهمزمان، کد را سادهتر کرده، وابستگی را کاهش داده و قابلیت نگهداری را بهبود میبخشد. با درک قابلیتها و محدودیتهای آن، میتوانید از AsyncLocalStorage برای ساخت برنامههای قوی، مقیاسپذیر و آگاه از مسائل جهانی استفاده کنید.
تسلط بر کانتکست ناهمزمان برای هر توسعهدهنده جاوا اسکریپتی که با کد ناهمزمان کار میکند، ضروری است. از AsyncLocalStorage و سایر تکنیکهای مدیریت کانتکست برای نوشتن برنامههای تمیزتر، قابل نگهداریتر و قابل اعتمادتر استقبال کنید.