لو فتحت Network tab في DevTools وأنت بتكتب في مربع البحث ولقيت 47 طلب fetch بترجع متلخبطة، المشكلة مش في الـ debounce ولا الـ backend البطيء. المشكلة إن المتصفح بيكمل تحميل طلبات أنت بقالك ثانيتين مش محتاجها. AbortController بيقطع الطلبات دي بسطر واحد، ويوفّر شبكة وذاكرة وقت استجابة فعلي.
المشكلة الحقيقية: مين قال إن الـ Promise بيتلغي لما تتجاهله؟
هنا فيه سوء فهم منتشر. لما تكتب fetch('/api/search?q=...') وتعمل بعدها fetch تاني للحرف الجديد، الطلب الأول مش بيقف. هو شغّال في الخلفية، الـ TCP connection لسه مفتوح، والسيرفر لسه بيعمل query على الـ database. JavaScript محتفظ بالـ Promise القديم في الذاكرة، وممكن يـ resolve بعد الجديد ويعرضلك نتيجة قديمة فوق نتيجة جديدة.
مثال تقريبي: الطبّاخ اللي مش بيرمي الطلبات الملغية
تخيّل مطعم فيه طبّاخ واحد. الزبون طلب مكرونة، الطبّاخ بدأ يحضرها. بعد دقيقة الزبون غيّر رأيه وقال «لا، عايز برجر». الطبّاخ كمل المكرونة وكمان عمل البرجر. النتيجة: الزبون استلم الاتنين، الطباخ مرهق، والمطبخ مكدّس. ده بالظبط اللي بيحصل في الـ frontend بدون cancellation.
الحل المنطقي: الجرسون يقول للطباخ «إلغي الطلب الأول». ده دور AbortController. هو الجرسون اللي بيبعت إشارة الإلغاء.
التعريف العلمي: Signal Pattern
الـ AbortController هو واجهة برمجية أُضيفت رسميًا للـ Web Platform في 2017، ودلوقتي مدعومة في كل المتصفحات الحديثة و Node.js منذ الإصدار 15. هي تطبيق لنمط معماري اسمه Signal Pattern أو Cooperative Cancellation: المُلغي ما بيفرضش الإلغاء بالقوة، هو بيرفع علم (signal) والطرف التاني (الـ fetch أو الـ stream) بيختار إنه يستجيب للعلم ده ويرمي AbortError.
الـ controller فيه خاصيتين أساسيتين:
controller.signal: الإشارة اللي بتتمرر للـ fetch.controller.abort(): الميثود اللي بترفع الإشارة دي.
أي API بيدعم AbortSignal — زي fetch, addEventListener, setTimeout (في Node)، Streams API — هيستجيب فورًا.
الكود قبل: الفوضى
// المشكلة: كل ضغطة زر بتعمل fetch جديد بدون إلغاء القديم
const input = document.querySelector('#search');
input.addEventListener('input', async (e) => {
const query = e.target.value;
const res = await fetch(`/api/search?q=${query}`);
const data = await res.json();
renderResults(data);
});