Treci la conținut

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

JavaScript
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:

JavaScript
ctrl.abort(new Error('user cancelled'));

fetch respinge cu orice valoare ați transmis.

Expirarea timpului

JavaScript
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). 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ă

JavaScript
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:

JavaScript
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

JavaScript
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 — interfață completă pentru cereri/răspunsuri/semnale destinată transporturilor personalizate.
  • API-uri web (WPT) — rata de promovare WPT pentru AbortController/AbortSignal.