
# AbortController-mønstre

`AbortController` og `AbortSignal` er fulde WHATWG-implementeringer i Phasis. Standardens kompositionshjælpere (`AbortSignal.any`, `AbortSignal.timeout`) følger også med. Denne side gennemgår de mønstre, du faktisk bruger, og hvordan annullering føres videre på tværs af grænsen PHP.

## Annullering af et fetch-kald

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

Årsagen er som standard `AbortError`, men du kan angive din egen:

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

`fetch` afviser med den værdi, du sendte.

## Timeout

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

`AbortSignal.timeout(ms)` returnerer et signal, der afbryder sig selv efter `ms`. Timeren udløses via motorens event loop, det samme som driver `setTimeout` / `queueMicrotask` (se [API: Event loop](https://phasis.inline0.dev/da/docs/api#event-loop)). Fetch-transporten aflæser signalet under overførslen — for standardtransporten `CurlTransport` via `CURLOPT_PROGRESSFUNCTION` — så anmodningen afbrydes hurtigt, når fristen udløber.

Løkken bruger som standard **virtuel tid**: den flytter sit ur frem til den næste ventende frist i stedet for at sove PHP, så et `fetch(url, { signal: AbortSignal.timeout(5000) })` enten afsluttes før fristen eller afbrydes ved næste løkketakt — den får aldrig PHP til at vente. Realtidstiming kræver, at værten driver `tickEventLoop()` efter et vægur.

## Race: den første annullering vinder

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

## Videregivelse gennem lagene

Pak dit eget asynkrone arbejde ind i en signalbevidst løkke:

```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-kaldet arver signalet direkte; den manuelle `if (signal.aborted)` dækker mellemrummene mellem anmodninger, hvor sideløkken ellers ville fortsætte.

## Lytning efter abort-hændelsen

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

`signal.reason` sættes, før lytterne kaldes.

## Signal i en brugerdefineret fetch-transport

Hvis du udskifter transporten via `setFetchTransport()`, sendes signalet til din callable som en `JsObject`. Respektér det ved at aflæse det:

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

Hvis du kaster `TransportException` med `kind: 'aborted'`, afviser JS-sidens `fetch` med `AbortError DOMException` — det samme som hvis specifikationens abort-løkke var blevet kørt.

For backends med indbygget annullering (Guzzles `on_progress`, Symfonys `ResponseInterface::cancel`) skal du tilkoble den samme aflæs-og-kast-struktur.

## Mønstre, der endnu ikke virker

- **`signal.addEventListener('abort', () => fetch(cleanupUrl))`** — lytterne kaldes synkront; oprydnings-fetch-kaldet starter, men bliver ikke færdigt under abort-dispatchen. Planlæg det via den omgivende `await`.

`setTimeout(() => ctrl.abort(), N)` virker (timerfamilien følger med og er dækket af WPT), men `AbortSignal.timeout(N)` er kortere og udtrykker hensigten tydeligere, når du blot skal bruge et signal, der afbryder efter en frist.

## Se også

- [Fetch-transport](https://phasis.inline0.dev/da/docs/interop/fetch-transport) — komplet grænseflade for anmodning/svar/signal til brugerdefinerede transporter.
- [Web-API'er (WPT)](https://phasis.inline0.dev/da/docs/compatibility/web-apis) — beståelsesprocenten for AbortController/AbortSignal i WPT.
