المستوى: للمتوسط (Intermediate)
لو search box عندك بيرسل طلب fetch لكل حرف بيكتبه المستخدم، أول 8 طلبات لسه شغّالة لما الطلب التاسع يبدأ. النتايج بترجع بترتيب عشوائي حسب سرعة الشبكة، وفي 15% من الحالات الـ UI بيعرض نتيجة كلمة قديمة بدل الكلمة اللي المستخدم شايفها قدامه. AbortController بـ 6 سطور بيحل المشكلة دي بصفر مكتبة خارجية.
AbortController: مفتاح إلغاء العمليات غير المتزامنة في JavaScript
المشكلة باختصار
الـ fetch في JavaScript لما بيبدأ ما عندوش زرار "إلغاء" بشكل مباشر. لو المستخدم غيّر الصفحة قبل ما الطلب يخلص، الكود بيستمر في معالجة الرد - وأحياناً بيحاول يكتب على state component اتعمله unmount. النتيجة: warnings في الـ console، memory leaks تدريجية، وbugs نادرة بتظهر بس على شبكات بطيئة.
مثال للمبتدئ: 3 طلبات بيتزا في نفس اللحظة
تخيّل إنك جعان وقررت تطلب بيتزا من 3 محلات في نفس الوقت بسبب التردّد. بعد دقيقتين أكلت ساندوتش من التلاجة. لما الديليفري الأول وصل، رفضت الطلب. التاني والتالت كمان رفضت ودفعت رسوم إلغاء. AbortController هو نفس الفكرة بالظبط: عندك "تحكم مركزي" في كل الطلبات، تقدر تتصل بكل المحلات مرة واحدة وتقولهم "إلغاء" قبل ما الديليفري يجي.
بدون AbortController، الـ JavaScript بيشبه إنك ما تقدرش تلغي - الـ 3 طلبات بتوصل لباب البيت والـ JS بيحاول يعالج رد كل واحد منهم على حدة. الأخير بيغطّي على الأول والصورة بتكسر.
التعريف العلمي الدقيق
AbortController واجهة معرّفة في DOM Living Standard (WHATWG) منذ 2017. هو كائن بيدير AbortSignal واحد. لما تستدعي controller.abort()، الـ signal بياخد حالة aborted = true، وأي API بيدعم AbortSignal (وده يشمل fetch, XMLHttpRequest, addEventListener, Streams، و setTimeout من Node 18+) بيتوقّف فوراً ويرمي DOMException اسمها AbortError.
الفكرة جايّة من Cancellation Tokens في .NET Task Parallel Library (Microsoft, 2009)، اتطوّرت في TC39 proposal اتسحب لصالح web-native API، وانضمّت رسمياً للـ Fetch Standard في 2017.
الكود التنفيذي - شغّال على Node 22 و Chrome 122
function createSearchHandler() {
let activeController = null;
return async function search(query) {
// ألغي أي طلب سابق لسه شغّال
if (activeController) activeController.abort();
activeController = new AbortController();
const { signal } = activeController;
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
signal
});
return await res.json();
} catch (err) {
if (err.name === 'AbortError') return null; // متجاهلش - ده طبيعي
throw err;
}
};
}
const search = createSearchHandler();
inputBox.addEventListener('input', (e) => search(e.target.value));