
# Modele AbortController

`AbortController` și `AbortSignal` sunt implementări WHATWG complete în Phasis. Sunt incluse și ajutoarele standard de compunere (`AbortSignal.any`, `AbortSignal.timeout`). Această pagină prezintă modelele folosite în practică și modul în care anularea se propagă peste limita PHP.

## Anularea unui fetch

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

Motivul implicit este `AbortError`, dar puteți furniza unul propriu:

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

`fetch` respinge cu orice valoare ați transmis.

## Expirarea timpului

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

`AbortSignal.timeout(ms)` returnează un semnal care se anulează singur după `ms`. Temporizatorul se declanșează prin bucla de evenimente a motorului, aceeași care coordonează `setTimeout` / `queueMicrotask` (consultați [API: Bucla de evenimente](https://phasis.inline0.dev/ro/docs/api#event-loop)). Transportul fetch verifică semnalul în timpul transferului — pentru `CurlTransport` implicit, prin `CURLOPT_PROGRESSFUNCTION` — astfel că solicitarea este anulată prompt la expirarea termenului.

Bucla folosește implicit **timp virtual**: avansează ceasul până la următorul termen limită în așteptare, în loc să aștepte PHP, astfel încât un `fetch(url, { signal: AbortSignal.timeout(5000) })` fie se încheie înainte de termenul limită, fie se întrerupe la următorul pas al buclei — nu face niciodată ca PHP să aștepte. Pentru ritmul în timp real, gazda trebuie să ruleze `tickEventLoop()` în raport cu un ceas real.

## Cursa: prima anulare câștigă

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

## Propagarea prin straturi

Încadrați propria activitate asincronă într-o buclă care ține cont de semnal:

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

Cererea fetch moștenește direct semnalul; verificarea manuală `if (signal.aborted)` acoperă intervalele dintre cereri în care bucla paginii ar continua altfel să ruleze.

## Ascultarea evenimentului de întrerupere

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

`signal.reason` este setat înainte să se declanșeze ascultătorii.

## Semnalul într-un transport fetch personalizat

Dacă schimbați transportul prin `setFetchTransport()`, semnalul este transmis funcției apelabile ca `JsObject`. Respectați-l verificând periodic:

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

Aruncarea lui `TransportException` cu `kind: 'aborted'` face ca JS-side `fetch` să fie respins cu `AbortError DOMException` — la fel ca și cum s-ar fi executat bucla de întrerupere din specificație.

Pentru backenduri cu anulare nativă (Guzzle's `on_progress`, Symfony's `ResponseInterface::cancel`), conectați aceeași logică de verificare și aruncare.

## Tipare care încă nu funcționează

- **`signal.addEventListener('abort', () => fetch(cleanupUrl))`** — declanșarea ascultătorilor este sincronă; cererea fetch de curățare pornește, dar nu se va încheia în timpul distribuirii evenimentului de întrerupere. Programați-o prin `await` din jur.

`setTimeout(() => ctrl.abort(), N)` funcționează (familia de temporizatoare este disponibilă și acoperită de WPT), dar `AbortSignal.timeout(N)` este mai scurt și exprimă mai bine intenția atunci când doriți doar un semnal de întrerupere după un termen limită.

## Consultați și

- [Transportul fetch](https://phasis.inline0.dev/ro/docs/interop/fetch-transport) — interfață completă pentru cereri/răspunsuri/semnale destinată transporturilor personalizate.
- [API-uri web (WPT)](https://phasis.inline0.dev/ro/docs/compatibility/web-apis) — rata de promovare WPT pentru AbortController/AbortSignal.
