راهنمای جامع AbortController در جاوا اسکریپت برای لغو کارآمد درخواستها، بهبود تجربه کاربری و عملکرد برنامه.
تسلط بر AbortController در جاوا اسکریپت: لغو یکپارچه درخواستها
در دنیای پویای توسعه وب مدرن، عملیات ناهمزمان ستون فقرات تجربیات کاربری واکنشگرا و جذاب است. از دریافت داده از APIها گرفته تا مدیریت تعاملات کاربر، جاوا اسکریپت به طور مکرر با وظایفی سروکار دارد که تکمیل آنها ممکن است زمانبر باشد. اما چه اتفاقی میافتد وقتی کاربر قبل از پایان یک درخواست به صفحه دیگری میرود، یا وقتی یک درخواست بعدی جایگزین درخواست قبلی میشود؟ بدون مدیریت صحیح، این عملیات در حال اجرا میتواند منجر به هدر رفتن منابع، دادههای منسوخ شده و حتی خطاهای غیرمنتظره شود. اینجاست که API JavaScript AbortController میدرخشد و یک مکانیزم قوی و استاندارد برای لغو عملیات ناهمزمان ارائه میدهد.
نیاز به لغو درخواست
یک سناریوی معمول را در نظر بگیرید: کاربری در یک نوار جستجو تایپ میکند و با هر ضربه کلید، برنامه شما یک درخواست API برای دریافت پیشنهادات جستجو ارسال میکند. اگر کاربر به سرعت تایپ کند، ممکن است چندین درخواست به طور همزمان در حال ارسال باشند. اگر کاربر در حالی که این درخواستها در انتظار هستند به صفحه دیگری برود، پاسخها، در صورت رسیدن، بیربط خواهند بود و پردازش آنها اتلاف منابع ارزشمند سمت کلاینت است. علاوه بر این، ممکن است سرور قبلاً این درخواستها را پردازش کرده باشد و هزینه محاسباتی غیرضروری را متحمل شود.
موقعیت رایج دیگر زمانی است که کاربر اقدامی مانند بارگذاری یک فایل را آغاز میکند، اما سپس تصمیم میگیرد آن را در میانه راه لغو کند. یا شاید یک عملیات طولانیمدت، مانند دریافت یک مجموعه داده بزرگ، دیگر مورد نیاز نباشد زیرا یک درخواست جدید و مرتبطتر ارسال شده است. در همه این موارد، توانایی خاتمه دادن به این عملیات در حال اجرا برای موارد زیر حیاتی است:
- بهبود تجربه کاربری: از نمایش دادههای کهنه یا نامربوط جلوگیری میکند، از بهروزرسانیهای غیرضروری UI اجتناب میکند و باعث میشود برنامه سریع و روان به نظر برسد.
- بهینهسازی استفاده از منابع: با عدم دانلود دادههای غیرضروری در پهنای باند صرفهجویی میکند، با عدم پردازش عملیات تکمیل شده اما غیرضروری، چرخههای CPU را کاهش میدهد و حافظه را آزاد میکند.
- جلوگیری از شرایط رقابتی (Race Conditions): تضمین میکند که فقط جدیدترین دادههای مرتبط پردازش میشوند و از سناریوهایی که پاسخ یک درخواست قدیمی و جایگزین شده، دادههای جدیدتر را بازنویسی کند، جلوگیری میکند.
معرفی AbortController API
رابط AbortController
راهی برای ارسال سیگنال درخواست لغو به یک یا چند عملیات ناهمزمان جاوا اسکریپت فراهم میکند. این رابط برای کار با APIهایی طراحی شده است که از AbortSignal
پشتیبانی میکنند، که برجستهترین آنها fetch
API مدرن است.
در هسته خود، AbortController
دو جزء اصلی دارد:
- نمونه
AbortController
: این شیئی است که شما برای ایجاد یک مکانیزم لغو جدید، نمونهسازی میکنید. - خاصیت
signal
: هر نمونهAbortController
یک خاصیتsignal
دارد که یک شیءAbortSignal
است. این شیءAbortSignal
همان چیزی است که شما به عملیات ناهمزمانی که میخواهید بتوانید لغو کنید، ارسال میکنید.
همچنین AbortController
یک متد واحد دارد:
abort()
: فراخوانی این متد بر روی یک نمونهAbortController
بلافاصلهAbortSignal
مرتبط را فعال کرده و آن را به عنوان لغو شده علامتگذاری میکند. هر عملیاتی که به این سیگنال گوش میدهد، مطلع شده و میتواند بر اساس آن عمل کند.
چگونه AbortController با Fetch کار میکند
استفاده از fetch
API اصلیترین و رایجترین مورد استفاده برای AbortController
است. هنگام ارسال یک درخواست fetch
، میتوانید یک شیء AbortSignal
را در شیء options
ارسال کنید. اگر سیگنال لغو شود، عملیات fetch
پیش از موعد خاتمه مییابد.
مثال پایه: لغو یک درخواست Fetch واحد
بیایید با یک مثال ساده این موضوع را توضیح دهیم. تصور کنید میخواهیم دادههایی را از یک API دریافت کنیم، اما میخواهیم بتوانیم این درخواست را لغو کنیم اگر کاربر تصمیم گرفت قبل از تکمیل آن به صفحه دیگری برود.
```javascript // Create a new AbortController instance const controller = new AbortController(); const signal = controller.signal; // The URL of the API endpoint const apiUrl = 'https://api.example.com/data'; console.log('Initiating fetch request...'); fetch(apiUrl, { signal: signal // Pass the signal to the fetch options }) .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(data => { console.log('Data received:', data); // Process the received data }) .catch(error => { if (error.name === 'AbortError') { console.log('Fetch request was aborted.'); } else { console.error('Fetch error:', error); } }); // Simulate cancelling the request after 5 seconds setTimeout(() => { console.log('Aborting fetch request...'); controller.abort(); // This will trigger the .catch block with an AbortError }, 5000); ```در این مثال:
- ما یک
AbortController
ایجاد کرده وsignal
آن را استخراج میکنیم. - ما این
signal
را به گزینههایfetch
ارسال میکنیم. - اگر
controller.abort()
قبل از تکمیل fetch فراخوانی شود، پرامیس بازگشتی توسطfetch
با یکAbortError
رد (reject) خواهد شد. - بلوک
.catch()
به طور خاص اینAbortError
را بررسی میکند تا بین یک خطای واقعی شبکه و یک لغو تمایز قائل شود.
نکته کاربردی: همیشه در بلوکهای catch
خود هنگام استفاده از AbortController
با fetch
، شرط error.name === 'AbortError'
را بررسی کنید تا لغوها را به درستی مدیریت کنید.
مدیریت چندین درخواست با یک کنترلر واحد
یک AbortController
واحد میتواند برای لغو چندین عملیات که همگی به signal
آن گوش میدهند، استفاده شود. این قابلیت برای سناریوهایی که یک اقدام کاربر ممکن است چندین درخواست در حال اجرا را نامعتبر کند، فوقالعاده مفید است. به عنوان مثال، اگر کاربری یک صفحه داشبورد را ترک کند، ممکن است بخواهید تمام درخواستهای دریافت داده در حال اجرا مربوط به آن داشبورد را لغو کنید.
در اینجا، هر دو عملیات fetch مربوط به 'Users' و 'Products' از یک signal
مشترک استفاده میکنند. هنگامی که controller.abort()
فراخوانی شود، هر دو درخواست خاتمه خواهند یافت.
دیدگاه جهانی: این الگو برای برنامههای پیچیده با کامپوننتهای زیادی که ممکن است به طور مستقل تماسهای API را آغاز کنند، بسیار ارزشمند است. به عنوان مثال، یک پلتفرم تجارت الکترونیک بینالمللی ممکن است کامپوننتهایی برای لیست محصولات، پروفایلهای کاربری و خلاصههای سبد خرید داشته باشد که همگی در حال دریافت داده هستند. اگر کاربر به سرعت از یک دسته محصول به دسته دیگر برود، یک فراخوانی abort()
واحد میتواند تمام درخواستهای در انتظار مربوط به نمای قبلی را پاکسازی کند.
شنونده رویداد (Event Listener) `AbortSignal`
در حالی که fetch
به طور خودکار سیگنال لغو را مدیریت میکند، سایر عملیات ناهمزمان ممکن است نیاز به ثبت صریح برای رویدادهای لغو داشته باشند. شیء AbortSignal
یک متد addEventListener
ارائه میدهد که به شما امکان میدهد به رویداد 'abort'
گوش دهید. این قابلیت به ویژه هنگام ادغام AbortController
با منطق ناهمزمان سفارشی یا کتابخانههایی که به طور مستقیم از گزینه signal
در پیکربندی خود پشتیبانی نمیکنند، مفید است.
در این مثال:
- تابع
performLongTask
یکAbortSignal
را میپذیرد. - این تابع یک interval برای شبیهسازی پیشرفت تنظیم میکند.
- نکته مهم این است که یک شنونده رویداد برای رویداد
'abort'
بهsignal
اضافه میکند. هنگامی که رویداد فعال میشود، interval را پاکسازی کرده و پرامیس را با یکAbortError
رد میکند.
نکته کاربردی: الگوی addEventListener('abort', callback)
برای منطق ناهمزمان سفارشی حیاتی است و تضمین میکند که کد شما میتواند به سیگنالهای لغو از خارج واکنش نشان دهد.
خاصیت `signal.aborted`
همچنین AbortSignal
یک خاصیت بولی به نام aborted
دارد که اگر سیگنال لغو شده باشد، true
و در غیر این صورت false
برمیگرداند. اگرچه این خاصیت مستقیماً برای شروع لغو استفاده نمیشود، اما میتواند برای بررسی وضعیت فعلی یک سیگنال در منطق ناهمزمان شما مفید باشد.
در این قطعه کد، signal.aborted
به شما امکان میدهد قبل از ادامه عملیات بالقوه منابعبر، وضعیت را بررسی کنید. در حالی که fetch
API این کار را به صورت داخلی انجام میدهد، منطق سفارشی ممکن است از چنین بررسیهایی بهرهمند شود.
فراتر از Fetch: موارد استفاده دیگر
در حالی که fetch
برجستهترین کاربر AbortController
است، پتانسیل آن به هر عملیات ناهمزمانی که بتواند برای گوش دادن به AbortSignal
طراحی شود، گسترش مییابد. این شامل موارد زیر است:
- محاسبات طولانیمدت: Web Workers، دستکاریهای پیچیده DOM، یا پردازش دادههای سنگین.
- تایمرها: اگرچه
setTimeout
وsetInterval
مستقیماًAbortSignal
را نمیپذیرند، میتوانید آنها را در پرامیسهایی که این کار را انجام میدهند بپیچید، همانطور که در مثالperformLongTask
نشان داده شد. - کتابخانههای دیگر: بسیاری از کتابخانههای مدرن جاوا اسکریپت که با عملیات ناهمزمان سروکار دارند (مانند برخی کتابخانههای دریافت داده، کتابخانههای انیمیشن) در حال ادغام پشتیبانی از
AbortSignal
هستند.
مثال: استفاده از AbortController با Web Workers
Web Workers برای انتقال وظایف سنگین از ترد اصلی عالی هستند. میتوانید با یک Web Worker ارتباط برقرار کرده و یک AbortSignal
به آن ارائه دهید تا امکان لغو کاری که در worker انجام میشود را فراهم کنید.
main.js
```javascript // Create a Web Worker const worker = new Worker('worker.js'); // Create an AbortController for the worker task const controller = new AbortController(); const signal = controller.signal; console.log('Sending task to worker...'); // Send the task data and the signal to the worker worker.postMessage({ task: 'processData', data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], signal: signal // Note: Signals cannot be directly transferred like this. // We need to send a message that the worker can use to // create its own signal or listen to messages. // A more practical approach is sending a message to abort. }); // A more robust way to handle signal with workers is via message passing: // Let's refine: We send a 'start' message, and an 'abort' message. worker.postMessage({ command: 'startProcessing', payload: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }); worker.onmessage = function(event) { console.log('Message from worker:', event.data); }; // Simulate aborting the worker task after 3 seconds setTimeout(() => { console.log('Aborting worker task...'); // Send an 'abort' command to the worker worker.postMessage({ command: 'abortProcessing' }); }, 3000); // Don't forget to terminate the worker when done // worker.terminate(); ```worker.js
```javascript let processingInterval = null; let isAborted = false; self.onmessage = function(event) { const { command, payload } = event.data; if (command === 'startProcessing') { isAborted = false; console.log('Worker received startProcessing command. Payload:', payload); let progress = 0; const total = payload.length; processingInterval = setInterval(() => { if (isAborted) { clearInterval(processingInterval); console.log('Worker: Processing aborted.'); self.postMessage({ status: 'aborted' }); return; } progress++; console.log(`Worker: Processing item ${progress}/${total}`); if (progress === total) { clearInterval(processingInterval); console.log('Worker: Processing complete.'); self.postMessage({ status: 'completed', result: 'Processed all items' }); } }, 500); } else if (command === 'abortProcessing') { console.log('Worker received abortProcessing command.'); isAborted = true; // The interval will clear itself on the next tick due to isAborted check. } }; ```توضیح:
- در ترد اصلی، ما یک
AbortController
ایجاد میکنیم. - به جای ارسال مستقیم
signal
(که امکانپذیر نیست زیرا یک شیء پیچیده است و به راحتی قابل انتقال نیست)، ما از ارسال پیام استفاده میکنیم. ترد اصلی یک فرمان'startProcessing'
و بعداً یک فرمان'abortProcessing'
ارسال میکند. - ورکر به این فرمانها گوش میدهد. هنگامی که
'startProcessing'
را دریافت میکند، کار خود را شروع کرده و یک interval تنظیم میکند. همچنین از یک پرچم به نامisAborted
استفاده میکند که توسط فرمان'abortProcessing'
مدیریت میشود. - هنگامی که
isAborted
به true تبدیل میشود، interval ورکر خود را پاکسازی کرده و گزارش میدهد که وظیفه لغو شده است.
نکته کاربردی: برای Web Workers، یک الگوی ارتباطی مبتنی بر پیام برای سیگنالدهی لغو پیادهسازی کنید تا به طور مؤثر رفتار یک AbortSignal
را شبیهسازی کند.
بهترین شیوهها و ملاحظات
برای بهرهبرداری مؤثر از AbortController
، این بهترین شیوهها را در نظر داشته باشید:
- نامگذاری واضح: از نامهای متغیر توصیفی برای کنترلرهای خود استفاده کنید (مانند
dashboardFetchController
،userProfileController
) تا آنها را به طور مؤثر مدیریت کنید. - مدیریت اسکوپ (Scope): اطمینان حاصل کنید که کنترلرها به درستی اسکوپبندی شدهاند. اگر یک کامپوننت unmount میشود، هرگونه درخواست در حال انتظار مرتبط با آن را لغو کنید.
- مدیریت خطا: همیشه بین
AbortError
و سایر خطاهای شبکه یا پردازش تمایز قائل شوید. - چرخه حیات کنترلر: یک کنترلر فقط یک بار میتواند لغو کند. اگر نیاز به لغو چندین عملیات مستقل در طول زمان دارید، به چندین کنترلر نیاز خواهید داشت. با این حال، یک کنترلر میتواند چندین عملیات را به طور همزمان لغو کند اگر همه آنها سیگنال آن را به اشتراک بگذارند.
- DOM AbortSignal: آگاه باشید که رابط
AbortSignal
یک استاندارد DOM است. در حالی که به طور گسترده پشتیبانی میشود، در صورت لزوم سازگاری با محیطهای قدیمیتر را تضمین کنید (اگرچه پشتیبانی در مرورگرهای مدرن و Node.js به طور کلی عالی است). - پاکسازی (Cleanup): اگر از
AbortController
در یک معماری مبتنی بر کامپوننت (مانند React، Vue، Angular) استفاده میکنید، اطمینان حاصل کنید کهcontroller.abort()
را در فاز پاکسازی (مانند `componentWillUnmount`، تابع بازگشتی `useEffect`، `ngOnDestroy`) فراخوانی میکنید تا از نشت حافظه و رفتار غیرمنتظره هنگام حذف یک کامپوننت از DOM جلوگیری شود.
دیدگاه جهانی: هنگام توسعه برای مخاطبان جهانی، تنوع در سرعت شبکه و تأخیر را در نظر بگیرید. کاربران در مناطقی با اتصال ضعیفتر ممکن است زمان درخواست طولانیتری را تجربه کنند، که این امر لغو مؤثر را برای جلوگیری از افت شدید تجربه آنها حیاتیتر میکند. طراحی برنامه شما با توجه به این تفاوتها کلیدی است.
نتیجهگیری
AbortController
و AbortSignal
مرتبط با آن ابزارهای قدرتمندی برای مدیریت عملیات ناهمزمان در جاوا اسکریپت هستند. با ارائه یک روش استاندارد برای سیگنالدهی لغو، آنها به توسعهدهندگان امکان میدهند برنامههای قویتر، کارآمدتر و کاربرپسندتری بسازند. چه با یک درخواست ساده fetch
سروکار داشته باشید و چه در حال سازماندهی گردشکارهای پیچیده باشید، درک و پیادهسازی AbortController
یک مهارت اساسی برای هر توسعهدهنده وب مدرن است.
تسلط بر لغو درخواست با AbortController
نه تنها عملکرد و مدیریت منابع را بهبود میبخشد، بلکه به طور مستقیم به تجربه کاربری برتر کمک میکند. هنگام ساخت برنامههای تعاملی، به یاد داشته باشید که این API حیاتی را برای مدیریت صحیح عملیات در حال انتظار ادغام کنید تا اطمینان حاصل شود که برنامههای شما در سراسر جهان برای همه کاربران واکنشگرا و قابل اعتماد باقی میمانند.