دستورات using و await using ساختارهای نحوی ویژهای هستند. سینتکسها بسیار راحت هستند و پیچیدگیهای زیادی را پنهان میکنند، اما گاهی اوقات در برنامهنویسی نیاز دارید که کارها را به صورت دستی و فراتر از محدودیتهای اسکوپ مدیریت کنید.
بیا یک مثال رایج را بررسی کنیم: چه میشود اگر نخواهید یک منبع را دقیقاً در پایان اسکوپِ فعلی آزاد کنید، بلکه بخواهید آن را در اسکوپ دیگری (کمی جلوتر یا بالاتر) پاکسازی کنید؟ به این کد نگاه کنید:
let reader;
if (someCondition) {
reader = stream.getReader();
} else {
reader = stream.getReader({ mode: "byob" });
}
همانطور که گفتیم، using عملکردی شبیه به const دارد؛ یعنی حتماً باید همان لحظه مقداردهی اولیه شود و نمیتوان آن را دوباره مقداردهی مجدد (Re-assign) کرد. پس اگر تلاش کنید کد بالا را اینطوری بنویسید:
if (someCondition) {
using reader = stream.getReader();
} else {
using reader = stream.getReader({ mode: "byob" });
}
با این کار، تمام منطق برنامه شما باید به ناچار داخل همان بلوکهای if یا else نوشته شود که باعث تکرار شدید کد (Code duplication) میگردد. چیزی که ما واقعاً میخواهیم این است که منبع را در یک اسکوپ (مثلاً درون شرط) بگیریم و ثبت کنیم، اما آزاد کردن آن را به یک اسکوپ بالاتر یا دیگر بسپاریم.
اینجاست که شیء DisposableStack به کمک ما میآید. این ابزار شیئی است که مجموعهای از منابع قابل پاکسازی را در خود نگه میدارد و خودش هم یک شیء Disposable (قابل پاکسازی) است:
{
using disposer = new DisposableStack();
let reader;
if (someCondition) {
// ثبت منبع در پشته پاکسازی
reader = disposer.use(stream.getReader());
} else {
reader = disposer.use(stream.getReader({ mode: "byob" }));
}
// انجام کارهای مختلف با reader
// قبل از خروج از این اسکوپ، شیء disposer پاکسازی میشود که خودکار متغیر reader را هم آزاد میکند.
}
adopt()گاهی با منبعی سروکار دارید که هنوز پروتکل Disposable (یعنی متد [Symbol.dispose]) را پیادهسازی نکرده است؛ در نتیجه اگر آن را مستقیماً به using بدهید خطا دریافت میکنید. در این حالت، میتوانید از متد adopt() استفاده کنید و تابع تصفیه را دستی به آن معرفی کنید:
{
using disposer = new DisposableStack();
// فرض کنید reader متد [Symbol.dispose]() را ندارد و با using کار نمیکند.
// اما ما میتوانیم به صورت دستی یک تابع تصفیه به متد disposer.adopt پاس بدهیم:
const reader = disposer.adopt(stream.getReader(), (reader) =>
reader.releaseLock(),
);
// انجام کارهای مختلف با reader
// قبل از خروج از اسکوپ، disposer پاکسازی شده و متد releaseLock را برای reader صدا میزند.
}
defer()شاید بخواهید در زمان اتمام اسکوپ و پاکسازی منابع، یک عملیات تصفیه یا کار جانبی انجام دهید که لزوماً به منبع خاصی "زنجیر" نشده است. مثلاً میخواهید وقتی چندین اتصال پایگاه داده به طور همزمان باز هستند، در پایان کار پیام «تمام اتصالات دیتابیس بسته شدند» را لاگ کنید. در این حالت از متد defer() استفاده میشود:
{
using disposer = new DisposableStack();
// ثبت یک کار جانبی برای پایان مسیر
disposer.defer(() => console.log("تمام اتصالات دیتابیس بسته شدند"));
const connection1 = disposer.use(openConnection());
const connection2 = disposer.use(openConnection());
// انجام کارهای مختلف با connection1 و connection2
// قبل از خروج از اسکوپ، disposer ابتدا connection2 و connection1 را تصفیه میکند و در نهایت آن پیام را لاگ میکند.
}
move()گاهی اوقات میخواهید پاکسازی را به صورت مشروط انجام دهید؛ مثلاً منابع گرفتهشده را فقط در صورتی که خطایی رخ داد آزاد کنید و اگر همهچیز خوب پیش رفت، آنها را حفظ کنید. در این حالت، میتوانید از متد move() استفاده کنید تا منابعی را که در صورت خروج از اسکوپ از بین میرفتند، به یک جای امن منتقل و حفظ کنید.
به این مثال فوقالعاده کاربردی در متد سازنده (Constructor) یک کلاس نگاه کنید:
class MyResource {
#resource1;
#resource2;
#disposables;
constructor() {
using disposer = new DisposableStack();
this.#resource1 = disposer.use(getResource1());
this.#resource2 = disposer.use(getResource2());
// اگر کد بدون خطا تا این خط اجرا شود، یعنی ساخت شیء با موفقیت انجام شده است.
// پس میتوانیم با خیال راحت منابع را از کنترل `disposer` خارج کرده و به `#disposables` منتقل کنیم:
this.#disposables = disposer.move();
// توجه: اگر در حین ساخت یکی از منابع خطایی رخ میداد، کد هرگز به خط بالا نمیرسید
// و جادوی disposer در همان لحظه تمام منابعی که تا آن لحظه گرفته شده بود را خودکار آزاد میکرد.
}
[Symbol.dispose]() {
this.#disposables.dispose(); // آزاد کردن نهایی `#resource2` و `#resource1` در زمان نابودی شیء کلاس
}
}
AsyncDisposableStackابزار AsyncDisposableStack دقیقاً مانند DisposableStack عمل میکند، اما برای کار با منابع ناهمگام (Async Disposable) طراحی شده است.
متد use() آن انتظار یک منبع با قابلیت پاکسازی ناهمگام را دارد.
متد adopt() آن یک تابع پاکسازی ناهمگام (Async Cleanup) میپذیرد.
متد defer() آن یک کالبک ناهمگام دریافت میکند.
این شیء متد [Symbol.asyncDispose]() را پیادهسازی میکند.
💡 یک نکته عالی: اگر ترکیبی از منابع همگام (Sync) و ناهمگام (Async) را در پروژه دارید، کماکان میتوانید منابع همگام را هم به پشتهی
AsyncDisposableStackپاس بدهید و بدون مشکل همه را یکجا مدیریت کنید.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://developer.mozilla.org/en-US/docs/Web/JavaScript