
# Padrões do AbortController

`AbortController` e `AbortSignal` são implementações WHATWG completas em Phasis. Os auxiliares padrão de composição (`AbortSignal.any`, `AbortSignal.timeout`) também estão incluídos. Esta página apresenta os padrões mais úteis e explica como o cancelamento se propaga pela fronteira PHP.

## Cancelar uma solicitação fetch

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

O motivo padrão é `AbortError`, mas você pode fornecer o seu próprio:

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

`fetch` rejeita com o valor que você passou.

## Timeout

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

`AbortSignal.timeout(ms)` retorna um sinal que é abortado após `ms`. O timer é acionado pelo loop de eventos do mecanismo, o mesmo que controla `setTimeout` / `queueMicrotask` (consulte [API: loop de eventos](https://phasis.inline0.dev/pt/docs/api#event-loop)). O transporte fetch verifica o sinal durante a transferência — no transporte padrão `CurlTransport`, por meio de `CURLOPT_PROGRESSFUNCTION` —, portanto a solicitação é abortada assim que o prazo é atingido.

Por padrão, o loop usa **tempo virtual**: adianta o relógio até o próximo prazo pendente em vez de dormir PHP. Assim, um `fetch(url, { signal: AbortSignal.timeout(5000) })` termina antes do prazo ou é abortado no próximo ciclo do loop — nunca faz PHP esperar. Para usar tempo real, o host precisa executar `tickEventLoop()` conforme o relógio de parede.

## Corrida: o primeiro cancelamento vence

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

## Propagação entre camadas

Envolva seu próprio trabalho assíncrono em um loop que verifica o sinal:

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

O fetch herda o sinal diretamente; o `if (signal.aborted)` manual cobre os intervalos entre solicitações em que o loop da página continuaria em execução.

## Como ouvir o evento de aborto

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

`signal.reason` é definido antes de os listeners serem acionados.

## Sinal em um transporte fetch personalizado

Se você substituir o transporte por meio de `setFetchTransport()`, o sinal será passado à sua função chamável como `JsObject`. Respeite-o verificando-o periodicamente:

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

Lançar `TransportException` com `kind: 'aborted'` faz a JS do lado `fetch` rejeitar com `AbortError DOMException` — como se o loop de aborto da especificação tivesse sido executado.

Para backends com cancelamento nativo (Guzzle's `on_progress`, Symfony's `ResponseInterface::cancel`), use a mesma estrutura de verificação e lançamento.

## Padrões que ainda não funcionarão

- **`signal.addEventListener('abort', () => fetch(cleanupUrl))`** — os listeners são acionados de forma síncrona; a solicitação fetch de limpeza começa, mas não termina durante o despacho do aborto. Agende-a pelo `await` ao redor.

`setTimeout(() => ctrl.abort(), N)` funciona (a família de timers está incluída e tem cobertura WPT), mas `AbortSignal.timeout(N)` é mais curto e expressa melhor a intenção quando você só quer um sinal de aborto após um prazo.

## Veja também

- [Transporte fetch](https://phasis.inline0.dev/pt/docs/interop/fetch-transport) — interface completa de solicitação/resposta/sinal para transportes personalizados.
- [APIs da Web (WPT)](https://phasis.inline0.dev/pt/docs/compatibility/web-apis) — taxa de aprovação nos testes WPT de AbortController/AbortSignal.
