
# AbortController šablonai

`AbortController` ir `AbortSignal` yra visavertės WHATWG realizacijos, įtrauktos į Phasis. Taip pat pateikiamos standartinės komponavimo pagalbinės funkcijos (`AbortSignal.any`, `AbortSignal.timeout`). Šiame puslapyje aptariami dažniausiai naudojami šablonai ir tai, kaip atšaukimas perduodamas per PHP ribą.

## Fetch užklausos atšaukimas

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

Pagal numatytuosius nustatymus priežastis yra `AbortError`, bet galite nurodyti savąją:

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

`fetch` atmetama su tuo, ką jai perdavėte.

## Skirtasis laikas

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

`AbortSignal.timeout(ms)` grąžina signalą, kuris pats nutraukiamas praėjus `ms`. Laikmatis suveikia variklio įvykių cikle – tame pačiame, kuris vykdo `setTimeout` / `queueMicrotask` (žr. [API: Įvykių ciklas](https://phasis.inline0.dev/lt/docs/api#event-loop)). Fetch transportas perdavimo metu tikrina signalą — numatytajam `CurlTransport` per `CURLOPT_PROGRESSFUNCTION` — todėl užklausa greitai nutraukiama, kai tik sueina terminas.

Pagal numatytuosius nustatymus ciklas naudoja **virtualų laiką**: jo laikrodis paslenkamas iki kito laukiančio termino, o ne realiuoju laiku laukiama PHP. Todėl `fetch(url, { signal: AbortSignal.timeout(5000) })` arba baigiama iki termino, arba nutraukiama per kitą ciklo žingsnį — PHP niekada nelaukia. Norint sinchronizuoti su realiuoju laiku, pagrindinė programa turi vykdyti `tickEventLoop()` pagal laikrodį.

## Lenktynės: laimi pirmasis atšaukimas

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

## Perdavimo grandinė per sluoksnius

Apgaubkite savo asinchroninį darbą signalo paisančiu ciklu:

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

Fetch užklausa signalą paveldi tiesiogiai; rankinis `if (signal.aborted)` padengia tarpus tarp užklausų, kai puslapio ciklas priešingu atveju veiktų toliau.

## Abort įvykio stebėjimas

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

`signal.reason` nustatomas prieš iškviečiant stebėtojus.

## Signalas tinkintame fetch transporte

Jei pakeisite transportą per `setFetchTransport()`, signalas jūsų iškviečiamajam objektui perduodamas kaip `JsObject`. Kad į jį būtų atsižvelgta, tikrinkite jį cikle:

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

Išmetus `TransportException` su `kind: 'aborted'`, JS pusės `fetch` atmetamas su `AbortError DOMException` — taip pat, kaip būtų suveikęs specifikacijoje aprašytas nutraukimo ciklas.

Gimtąjį atšaukimą palaikančioms posistemėms (Guzzle `on_progress`, Symfony `ResponseInterface::cancel`) prijunkite tokią pačią tikrinimo ir klaidos išmetimo logiką.

## Šablonai, kurie kol kas neveiks

- **`signal.addEventListener('abort', () => fetch(cleanupUrl))`** — stebėtojas iškviečiamas sinchroniškai; valymo fetch užklausa pradedama, bet abort iškvietimo metu nebus užbaigta. Suplanuokite ją naudodami aplinkinį `await`.

`setTimeout(() => ctrl.abort(), N)` veikia (laikmačių grupė įtraukta ir patikrinta WPT), tačiau `AbortSignal.timeout(N)` trumpesnis ir geriau nusako tikslą, kai reikia tik signalo, kuris nutraukia darbą pasibaigus terminui.

## Taip pat žr.

- [Fetch transportas](https://phasis.inline0.dev/lt/docs/interop/fetch-transport) — visavertė užklausos, atsakymo ir signalo sąsaja tinkintiems transportams.
- [Žiniatinklio API (WPT)](https://phasis.inline0.dev/lt/docs/compatibility/web-apis) — AbortController/AbortSignal WPT išlaikymo rodiklis.
