راهحل پیشرفتهای که جاوااسکریپت برای حل این مشکل ارائه داده، دو نوع متغیر اعلامیِ ویژه است: using و await using. این دو ساختار شباهت زیادی به const دارند، اما تفاوت طلاییشان این است که به محض خارج شدن متغیر از محدوده دسترسی (Scope)، اگر آن منبع قابلیت پاکسازی داشته باشد (Disposable باشد)، به طور خودکار آن را آزاد میکنند.
با استفاده از همان مثال قبلی استریم، میتوانیم کد را به این شکلِ ساده و تمیز بازنویسی کنیم:
{
using reader1 = stream1.getReader();
using reader2 = stream2.getReader();
// انجام کارهای مختلف با reader1 و reader2
// درست قبل از خارج شدن از این بلوک {}، منابع reader1 و reader2 به طور خودکار آزاد میشوند!
}
📌 نکته: در زمان نگارش این مستندات،
ReadableStreamDefaultReaderهنوز پروتکل تفکیک و پاکسازی (Disposable Protocol) را پیادهسازی نکرده است؛ بنابراین مثال بالا صرفاً جنبهی فرضی و آموزشی دارد.
اول از همه، به آکاردئونها یا همان ابروهای باز و بسته { } اضافه در اطراف کد توجه کنید. این کار یک محدوده بلوکی (Block Scope) جدید برای دستورات using ایجاد میکند. منابعی که با using تعریف میشوند، به محض خارج شدن از این اسکوپ به طور خودکار آزاد میشوند؛ در این مثال، خروج از اسکوپ یعنی هر زمان که از بلوک خارج شویم (چه به خاطر اجرای کامل تمام دستورات، چه به خاطر برخورد با یک خطا، یا مواجهه با دستوراتی مثل return ،break ،continue).
این یعنی دستور using فقط در محدودههایی قابل استفاده است که طول عمر (Lifetime) کاملاً مشخصی دارند. به همین دلیل:
نمیتوان از آن در بالاترین سطح (Top-level) یک اسکریپت معمولی استفاده کرد؛ چون متغیرهای تاپلولِ اسکریپت، در اسکوپ تمام اسکریپتهای بعدی صفحه هم باقی میمانند و این یعنی اگر صفحه هرگز بسته نشود، آن منبع هم هیچوقت آزاد نخواهد شد.
اما میتوان از آن در بالاترین سطح یک ماژول (Module) استفاده کرد؛ چون اسکوپ ماژول به محض پایان یافتن اجرای آن ماژول تمام میشود.
دستور using برای انجام کارش نیاز دارد که آن منبع، «پروتکل پاکسازی» (Disposable Protocol) را پیادهسازی کرده باشد. یک شیء زمانی Disposable است که متدی به نام [Symbol.dispose]() داشته باشد. این متد بدون هیچ آرگومانی برای انجام عملیات پاکسازی صدا زده میشود.
مثلاً در نمونهی مربوط به reader، ویژگی [Symbol.dispose] میتواند یک رپِر (Wrapper) یا یک نام مستعار (Alias) ساده برای همان متد قدیمی releaseLock باشد:
// روش اول: نوشتن یک رپر (پوشش کپسولهشده) - برای نمایش مفهوم
class MyReader {
[Symbol.dispose]() {
this.releaseLock();
}
releaseLock() {
// منطق اصلی آزاد کردن منابع
}
}
// روش دوم: تعریف به عنوان نام مستعار (Alias)
MyReader.prototype[Symbol.dispose] = MyReader.prototype.releaseLock;
به لطف پروتکل یکپارچهی Disposable، دستور using میتواند بدون اینکه اصلاً بداند با چه نوع منبعی (فایل، شبکه، استریم و...) طرف است، همهی آنها را به یک روشِ هماهنگ و استاندارد پاکسازی کند.
هر اسکوپ و محدوده، لیستی از منابع مرتبط با خودش را دارد که به همان ترتیبِ تعریف شدنشان در کد ثبت میشوند. وقتی اجرای اسکوپ به پایان میرسد، منابع با ترتیب معکوس (از آخر به اول) و با صدا زدن متد [Symbol.dispose]() آنها پاکسازی میشوند.
در مثال بالا، چون reader1 قبل از reader2 تعریف شده است، در زمان خروج از بلوک، ابتدا reader2 پاکسازی میشود و سپس reader1.
نکته عالی اینجاست که اگر در زمان پاکسازیِ یک منبع خطایی رخ دهد و اِکسپشن پرتاب شود، این خطا جلوی پاکسازی بقیه منابع را نمیگیرد. این رفتار دقیقاً با الگوی try...finally تودرتو مطابقت دارد و به وابستگیهای احتمالی بین منابع احترام میگذارد.
await usingساختار await using بسیار شبیه به using است. نام این سینتکس به شما میگوید که یک جادوی await در پسزمینه اتفاق میافتد؛ اما نه در زمان تعریف متغیر، بلکه دقیقاً در زمانِ پاکسازی و آزاد شدن آن منبع!
دستور await using نیاز دارد که منبع شما پروتکل پاکسازی ناهمگام (Async Disposable) را پیادهسازی کرده باشد؛ یعنی شیء باید متدی به نام [Symbol.asyncDisposable]() داشته باشد. این متد بدون آرگومان صدا زده میشود و یک پرامیس (Promise) برمیگرداند و سیستم منتظر میماند تا این پرامیس مستقر (Resolve) شود تا فرآیند پاکسازی تمام گردد.
این قابلیت برای زمانهایی که عملیات بسته شدن منبع ناهمگام است (مثل متد fileHandle.close()) فوقالعاده است؛ چرا که نتیجهی نهایی پاکسازی صرفاً به صورت ناهمگام مشخص میشود:
{
await using fileHandle = open("file.txt", "w");
await fileHandle.write("Hello");
// در اینجا متد fileHandle.close() صدا زده میشود و سیستم منتظر پایان آن (awaited) میماند
}
چون دستور await using نیاز به اجرای فرآیندِ await دارد، فقط در محیطهایی مجاز است که استفاده از await در آنها قانونی باشد؛ یعنی داخل توابع ناهمگام (async functions) و محیطهای top-level await در ماژولها.
⚠️ نکته فنی: منابع ناهمگام به صورت پیاپی و توالی (Sequentially) پاکسازی میشوند، نه به صورت همزمان (Concurrently). یعنی سیستم منتظر حل شدن پرامیسِ متد
[Symbol.asyncDispose]()یک منبع میماند و بعد از آن به سراغ منبع بعدی میرود.
استفاده از این قابلیت کاملاً انتخابی (Opt-in) است: اگر منابع خود را با دستورات معمولیِ let ،const یا var تعریف کنید، هیچ پاکسازی خودکاری رخ نمیدهد؛ درست مثل بقیه مقادیر معمولی.
نیاز به پیادهسازی متد اختصاصی: دستورات using و await using حتماً نیاز دارند که منبع به ترتیب دارای متدهای [Symbol.dispose] یا [Symbol.asyncDispose] باشد. اگر این متدها وجود نداشته باشند، در همان خطِ تعریف متغیر با خطای TypeError مواجه میشوید. البته مقدار منبع میتواند null یا undefined باشد که این موضوع به شما اجازه میدهد منابع را به صورت مشروط (Conditional) دریافت کنید.
غیرقابل مقداردهی مجدد: درست مثل const، متغیرهایی که با using و await using تعریف میشوند را نمیتوان دوباره مقداردهی کرد (Re-assign کرد)، هرچند ویژگیها و پراپرتیهای داخلی اشیای آنها قابل تغییر است. با این حال، متد پاکسازی در همان لحظهی تعریف متغیر در حافظه ذخیره میشود؛ پس اگر بعد از تعریف متغیر، متد تصفیه آن را تغییر دهید، تغییری در نحوه پاکسازی نهایی منبع ایجاد نمیشود.
⚠️ یک هشدار: گره زدن طول عمر منبع به محدوده اسکوپ، گاهی اوقات میتواند تلهها و چالشهای ظریفی ایجاد کند (برای بررسی چند نمونه از این چالشها میتوانید به بخش مرجع تخصصی
usingمراجعه کنید).
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://developer.mozilla.org/en-US/docs/Web/JavaScript