انتقل إلى المحتوى

أنماط AbortController

تُنفذ AbortController وAbortSignal بالكامل وفق WHATWG في Phasis. كما تتوفر أدوات التركيب القياسية (AbortSignal.any وAbortSignal.timeout). تتناول هذه الصفحة الأنماط المستخدمة فعليًا وكيف ينتشر الإلغاء عبر الحد الفاصل بين PHP.

إلغاء طلب fetch

JavaScript
const ctrl = new AbortController();
const p = fetch(url, { signal: ctrl.signal });
ctrl.abort();                     // p rejects with AbortError DOMException

السبب هو AbortError افتراضيًا، لكن يمكنك تحديد سببك:

JavaScript
ctrl.abort(new Error('user cancelled'));

يرفض fetch بالقيمة التي مررتها إليه.

المهلة الزمنية

JavaScript
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });

يعيد AbortSignal.timeout(ms) إشارة تلغي نفسها بعد ms. يعمل المؤقت عبر حلقة أحداث المحرك نفسها التي تشغّل setTimeout/queueMicrotask (راجع API: حلقة الأحداث). يتحقق ناقل fetch من الإشارة أثناء النقل — وبالنسبة إلى CurlTransport الافتراضي، عبر CURLOPT_PROGRESSFUNCTION — لذا يُلغى الطلب سريعًا عند حلول الموعد النهائي.

تعمل الحلقة وفق الوقت الافتراضي افتراضيًا: تقدّم ساعتها إلى الموعد النهائي المعلّق التالي بدلًا من الانتظار PHP، لذا إما أن يكتمل fetch(url, { signal: AbortSignal.timeout(5000) }) قبل الموعد النهائي أو يُلغى عند نبضة الحلقة التالية — ولا يجعل PHP ينتظر. يتطلب ضبط الإيقاع وفق الوقت الفعلي أن يقود المضيف tickEventLoop() وفق ساعة فعلية.

التسابق: الإلغاء الأول هو الفائز

JavaScript
const user = new AbortController();
const signal = AbortSignal.any([user.signal, AbortSignal.timeout(10_000)]);
const res = await fetch(url, { signal });
// res rejects if EITHER the user clicks cancel OR 10s elapses

الانتشار عبر الطبقات

غلّف عملك غير المتزامن بحلقة تراعي الإشارة:

JavaScript
async function fetchPaged(url, signal) {
  const out = [];
  for (let page = 1; ; page++) {
    if (signal.aborted) throw signal.reason;
    const r = await fetch(`${url}?page=${page}`, { signal });
    const data = await r.json();
    if (data.length === 0) break;
    out.push(...data);
  }
  return out;
}

يرث طلب fetch الإشارة مباشرة؛ أما if (signal.aborted) اليدوي فيغطي الفجوات بين الطلبات التي قد تواصل فيها حلقة الصفحة التنفيذ لولا ذلك.

الاستماع إلى حدث الإلغاء

JavaScript
ctrl.signal.addEventListener('abort', () => {
  releaseDatabaseLock();
  cancelDownstreamWork();
});

تُضبط signal.reason قبل تشغيل المستمعين.

الإشارة في ناقل fetch مخصص

إذا بدّلت وسيلة النقل عبر setFetchTransport()، فستُمرر الإشارة إلى الدالة القابلة للاستدعاء لديك بوصفها JsObject. احترمها بالتحقق منها دوريًا:

PHP
$engine->setFetchTransport(function (array $req, $signal) {
    $ch = curl_init($req['url']);
    // ...
    if ($signal !== null) {
        curl_setopt($ch, CURLOPT_PROGRESSFUNCTION,
            fn () => $signal->get('aborted')->value ? 1 : 0);
        curl_setopt($ch, CURLOPT_NOPROGRESS, false);
    }
    $body = curl_exec($ch);
    if (curl_errno($ch) === CURLE_ABORTED_BY_CALLBACK) {
        throw new \Phasis\BuiltIn\Fetch\TransportException(
            'aborted', 'aborted',
        );
    }
    // ...
});

يؤدي إطلاق TransportException باستخدام kind: 'aborted' إلى رفض fetch في JS بقيمة AbortError DOMException — كما لو نُفذت حلقة الإلغاء المحددة في المواصفات.

بالنسبة إلى الواجهات الخلفية ذات الإلغاء الأصلي (on_progress من Guzzle وResponseInterface::cancel من Symfony)، استخدم نمط التحقق ثم الإطلاق نفسه.

أنماط لا تعمل بعد

  • signal.addEventListener('abort', () => fetch(cleanupUrl)) — تشغيل المستمع متزامن؛ يبدأ طلب fetch للتنظيف لكنه لن يكتمل أثناء إرسال حدث الإلغاء. جدوِله عبر await المحيطة.

يعمل setTimeout(() => ctrl.abort(), N) (عائلة المؤقتات متاحة ومغطاة باختبارات WPT)، لكن AbortSignal.timeout(N) أقصر ويعبّر عن المقصود بوضوح أكبر عندما تريد فقط إشارة إلغاء بعد الموعد النهائي.

راجع أيضًا

  • ناقل Fetch — واجهة كاملة للطلب والاستجابة والإشارة للناقلات المخصصة.
  • واجهات الويب (WPT) — معدل اجتياز اختبارات WPT لـ AbortController/AbortSignal.