
# AbortController-minták

A `AbortController` és a `AbortSignal` teljes WHATWG-megvalósítás a Phasis-ben. A szabványos kompozíciós segédek (`AbortSignal.any`, `AbortSignal.timeout`) is rendelkezésre állnak. Ez az oldal a gyakran használt mintákat mutatja be, és azt, hogyan terjed tovább a megszakítás az PHP határán.

## Fetch megszakítása

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

Az ok alapértelmezés szerint `AbortError`, de megadhat sajátot:

```js
ctrl.abort(new Error('user cancelled'));
```

A `fetch` azzal utasítja el a kérést, amit átadott neki.

## Időkorlát

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

A `AbortSignal.timeout(ms)` olyan jelet ad vissza, amely `ms` elteltével önmagát megszakítja. Az időzítő a motor eseményciklusán keresztül lép működésbe, ugyanazon keresztül, amely a `setTimeout` / `queueMicrotask` elemeket is vezérli (lásd: [API: Eseményciklus](https://phasis.inline0.dev/hu/docs/api#event-loop)). A fetch-átviteli mód az átvitel során ellenőrzi a jelet — az alapértelmezett `CurlTransport` esetén a `CURLOPT_PROGRESSFUNCTION` segítségével —, így a kérés a határidő lejártakor gyorsan megszakad.

Az eseményciklus alapértelmezés szerint **virtuális idejű**: a következő függő határidőre állítja az órát, ahelyett hogy PHP-ig várakozna, így a `fetch(url, { signal: AbortSignal.timeout(5000) })` vagy a határidő előtt fejeződik be, vagy az eseményciklus következő lépésénél megszakad — a PHP-nek sosem kell várnia. A valós idejű ütemezéshez a gazdának falióra szerint kell vezérelnie az `tickEventLoop()`-et.

## Verseny: az első megszakítás nyer

```js
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
```

## Továbbterjedés a rétegeken át

Jelérzékeny ciklussal csomagolja be a saját aszinkron feladatait:

```js
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;
}
```

A fetch közvetlenül örökli a jelet; a kézi `if (signal.aborted)` kezeli a kérések közötti szüneteket, amikor az oldal ciklusa egyébként tovább futna.

## Figyelés a megszakítási eseményre

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

A `signal.reason` már be van állítva, mielőtt a figyelők lefutnak.

## Jel egy egyéni fetch-átviteli módban

Ha az átviteli módot a `setFetchTransport()` használatával cseréli le, a jel `JsObject` formájában jut el a hívható elemhez. A megszakítás érvényesítéséhez rendszeresen ellenőrizze:

```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',
        );
    }
    // ...
});
```

Ha `kind: 'aborted'`-gyel `TransportException`-t dob, a JS oldali `fetch` `AbortError DOMException`-vel utasítja el a kérést — ugyanúgy, mintha a specifikáció megszakítási ciklusa lefutott volna.

A natív megszakítást támogató háttérrendszerek esetén (a Guzzle `on_progress`-ja, illetve a Symfony `ResponseInterface::cancel`-ja) alkalmazza ugyanezt az ellenőrzési és kivételdobási mintát.

## Még nem működő minták

- **`signal.addEventListener('abort', () => fetch(cleanupUrl))`** — a figyelők szinkron módon futnak le; a takarító fetch elindul, de a megszakítás eseményének feldolgozása alatt nem fejeződik be. Ütemezze a környező `await` segítségével.

A `setTimeout(() => ctrl.abort(), N)` működik (az időzítők rendelkezésre állnak, és a WPT lefedi őket), de a `AbortSignal.timeout(N)` rövidebb, és jobban kifejezi a szándékot, ha csak egy határidő után megszakító jelre van szüksége.

## Lásd még

- [Fetch-átvitel](https://phasis.inline0.dev/hu/docs/interop/fetch-transport) — teljes kérés-/válasz-/jelinterfész egyéni átviteli módokhoz.
- [Webes API-k (WPT)](https://phasis.inline0.dev/hu/docs/compatibility/web-apis) — az AbortController/AbortSignal WPT-sikerességi aránya.
