
# Vzory používania AbortController

`AbortController` a `AbortSignal` sú plnohodnotné implementácie WHATWG v Phasis. Súčasťou sú aj štandardné pomocné funkcie na skladanie (`AbortSignal.any`, `AbortSignal.timeout`). Táto stránka sa venuje vzorom, ktoré budete skutočne používať, a tomu, ako sa zrušenie šíri cez hranicu PHP.

## Zrušenie požiadavky fetch

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

Dôvodom je predvolene `AbortError`, môžete však zadať vlastný:

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

`fetch` zamietne požiadavku s ľubovoľnou odovzdanou hodnotou.

## Časový limit

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

`AbortSignal.timeout(ms)` vráti signál, ktorý sa zruší po uplynutí `ms`. Časovač sa spustí cez slučku udalostí enginu, tú istú, ktorá riadi `setTimeout` / `queueMicrotask` (pozrite [API: Slučka udalostí](https://phasis.inline0.dev/sk/docs/api#event-loop)). Transport fetch kontroluje signál počas prenosu — pri predvolenom `CurlTransport` prostredníctvom `CURLOPT_PROGRESSFUNCTION` — takže požiadavka sa zruší hneď po uplynutí časového limitu.

Slučka štandardne používa **virtuálny čas**: posunie hodiny na najbližší čakajúci termín namiesto toho, aby skutočne čakala PHP. `fetch(url, { signal: AbortSignal.timeout(5000) })` sa preto buď dokončí pred termínom, alebo sa zruší pri nasledujúcom kroku slučky — PHP nikdy nemusí čakať. Ak chcete riadenie v reálnom čase, hostiteľ musí spúšťať `tickEventLoop()` podľa hodín reálneho času.

## Súťaž: vyhráva prvé zrušenie

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

## Šírenie cez vrstvy

Obaľte vlastnú asynchrónnu prácu slučkou, ktorá reaguje na signál:

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

Požiadavka fetch preberá signál priamo; manuálny `if (signal.aborted)` pokrýva medzery medzi požiadavkami, počas ktorých by slučka stránky inak pokračovala.

## Počúvanie udalosti zrušenia

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

`signal.reason` sa nastaví pred spustením poslucháčov.

## Signál vo vlastnom transporte fetch

Ak zmeníte transport cez `setFetchTransport()`, volateľný objekt dostane signál ako `JsObject`. Rešpektujte ho kontrolovaním:

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

Vyvolaním `TransportException` s `kind: 'aborted'` dosiahnete, že JS-strana `fetch` zamietne požiadavku s `AbortError DOMException` — rovnako, ako keby sa spustila špecifikačná slučka rušenia.

V prípade backendov s natívnym rušením (Guzzle `on_progress`, Symfony `ResponseInterface::cancel`) použite rovnaký vzor kontroly a vyvolania chyby.

## Vzory, ktoré zatiaľ nefungujú

- **`signal.addEventListener('abort', () => fetch(cleanupUrl))`** — poslucháče sa spúšťajú synchrónne; požiadavka fetch na upratanie sa začne, ale počas spracovania udalosti zrušenia sa nedokončí. Naplánujte ju cez nadradené `await`.

`setTimeout(() => ctrl.abort(), N)` funguje (skupina časovačov je súčasťou implementácie a pokrývajú ju testy WPT), ale `AbortSignal.timeout(N)` je stručnejšie a lepšie vystihuje zámer, keď potrebujete iba signál na zrušenie po uplynutí časového limitu.

## Pozrite si aj

- [Transport fetch](https://phasis.inline0.dev/sk/docs/interop/fetch-transport) — úplné rozhranie požiadaviek, odpovedí a signálov pre vlastné transporty.
- [Webové API (WPT)](https://phasis.inline0.dev/sk/docs/compatibility/web-apis) — miera úspešnosti testov WPT pre AbortController/AbortSignal.
