
# Pola AbortController

`AbortController` dan `AbortSignal` adalah implementasi WHATWG lengkap di Phasis. Helper komposisi standar (`AbortSignal.any`, `AbortSignal.timeout`) juga tersedia. Halaman ini membahas pola yang umum digunakan dan cara pembatalan merambat melintasi batas PHP.

## Membatalkan fetch

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

Alasannya secara bawaan adalah `AbortError`, tetapi Anda dapat memberikan alasan sendiri:

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

`fetch` menolak dengan nilai apa pun yang Anda berikan.

## Batas waktu

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

`AbortSignal.timeout(ms)` mengembalikan sinyal yang membatalkan dirinya sendiri setelah `ms`. Timer berjalan melalui event loop mesin yang sama dengan yang menjalankan `setTimeout` / `queueMicrotask` (lihat [API: Event loop](https://phasis.inline0.dev/id/docs/api#event-loop)). Transportasi fetch memeriksa sinyal selama transfer — untuk `CurlTransport` bawaan, melalui `CURLOPT_PROGRESSFUNCTION` — sehingga permintaan segera dibatalkan saat tenggat tercapai.

Secara bawaan, loop menggunakan **waktu virtual**: jamnya maju ke tenggat tertunda berikutnya alih-alih menunggu PHP, sehingga `fetch(url, { signal: AbortSignal.timeout(5000) })` selesai sebelum tenggat atau dibatalkan pada tick loop berikutnya — tidak pernah membuat PHP menunggu. Pengaturan waktu nyata mengharuskan host menjalankan `tickEventLoop()` berdasarkan jam dinding.

## Race: pembatalan pertama menang

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

## Propagasi melalui lapisan

Bungkus pekerjaan async Anda sendiri dengan loop yang memperhatikan sinyal:

```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 mewarisi sinyal secara langsung; `if (signal.aborted)` manual menangani jeda antarpermintaan saat loop halaman mungkin terus berjalan.

## Mendengarkan event pembatalan

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

`signal.reason` ditetapkan sebelum listener dijalankan.

## Sinyal dalam transportasi fetch khusus

Jika Anda mengganti transportasi melalui `setFetchTransport()`, sinyal diteruskan ke callable Anda sebagai `JsObject`. Patuhi sinyal dengan memeriksanya secara berkala:

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

Melempar `TransportException` dengan `kind: 'aborted'` membuat JS-side `fetch` menolak dengan `AbortError DOMException` — sama seperti jika abort-loop spesifikasi dijalankan.

Untuk backend dengan pembatalan bawaan (Guzzle milik `on_progress`, Symfony milik `ResponseInterface::cancel`), gunakan pola pemeriksaan dan pelemparan yang sama.

## Pola yang belum berfungsi

- **`signal.addEventListener('abort', () => fetch(cleanupUrl))`** — listener dijalankan secara sinkron; fetch pembersihan dimulai tetapi tidak akan selesai saat event pembatalan diproses. Jadwalkan melalui `await` di sekitarnya.

`setTimeout(() => ctrl.abort(), N)` berfungsi (keluarga timer tersedia dan tercakup WPT), tetapi `AbortSignal.timeout(N)` lebih ringkas dan lebih jelas menyampaikan maksud ketika Anda hanya memerlukan sinyal untuk membatalkan setelah tenggat.

## Lihat juga

- [Transportasi fetch](https://phasis.inline0.dev/id/docs/interop/fetch-transport) — antarmuka lengkap permintaan/respons/sinyal untuk transportasi khusus.
- [API Web (WPT)](https://phasis.inline0.dev/id/docs/compatibility/web-apis) — tingkat kelulusan WPT AbortController/AbortSignal.
