Patrones de AbortController
AbortController y AbortSignal son implementaciones completas de WHATWG en Phasis. También incluyen los ayudantes de composición estándar (AbortSignal.any, AbortSignal.timeout). Esta página explica los patrones más habituales y cómo se propaga la cancelación a través del límite de PHP.
Cancelar una solicitud Fetch
const ctrl = new AbortController();
const p = fetch(url, { signal: ctrl.signal });
ctrl.abort(); // p rejects with AbortError DOMException
De forma predeterminada, el motivo es AbortError, pero puedes indicar otro:
ctrl.abort(new Error('user cancelled'));
fetch se rechaza con el valor que le hayas pasado.
Tiempo de espera
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
AbortSignal.timeout(ms) devuelve una señal que se aborta por sí sola después de ms. El temporizador se activa mediante el bucle de eventos del motor, el mismo que impulsa setTimeout / queueMicrotask (consulta API: Bucle de eventos). Durante la transferencia, el transporte Fetch consulta la señal; con el CurlTransport predeterminado, lo hace mediante CURLOPT_PROGRESSFUNCTION, así que la solicitud se aborta rápidamente cuando vence el plazo.
El bucle usa tiempo virtual de forma predeterminada: adelanta el reloj hasta el siguiente plazo pendiente en lugar de esperar PHP, así que fetch(url, { signal: AbortSignal.timeout(5000) }) se completa antes del plazo o se cancela en el siguiente ciclo del bucle; nunca hace que PHP espere. Para avanzar en tiempo real, el host debe ejecutar tickEventLoop() según el reloj real.
Carrera: gana la primera cancelación
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
Propagación entre capas
Envuelve tu propio trabajo asíncrono en un bucle que tenga en cuenta la señal:
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;
}
La solicitud fetch hereda la señal directamente; el if (signal.aborted) manual cubre los intervalos entre solicitudes en los que, de otro modo, el bucle de página seguiría ejecutándose.
Escuchar el evento de cancelación
ctrl.signal.addEventListener('abort', () => {
releaseDatabaseLock();
cancelDownstreamWork();
});
signal.reason se establece antes de que se ejecuten los listeners.
Señal en un transporte fetch personalizado
Si cambias el transporte mediante setFetchTransport(), la señal se pasa a tu función como JsObject. Respétala comprobándola periódicamente:
$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',
);
}
// ...
});
Lanzar TransportException con kind: 'aborted' hace que el JS del lado de fetch se rechace con AbortError DOMException, igual que si se hubiera ejecutado el bucle de cancelación de la especificación.
Para los backends con cancelación nativa (Guzzle de on_progress, ResponseInterface::cancel de Symfony), implementa el mismo patrón de comprobación y lanzamiento.
Patrones que aún no funcionan
signal.addEventListener('abort', () => fetch(cleanupUrl))— la ejecución de los listeners es síncrona; la solicitud fetch de limpieza comienza, pero no puede completarse durante el envío de la cancelación. Prográmala mediante elawaitque la rodea.
setTimeout(() => ctrl.abort(), N) funciona (la familia de temporizadores está incluida y cuenta con cobertura de WPT), pero AbortSignal.timeout(N) es más breve y expresa mejor la intención cuando solo necesitas una señal de cancelación al vencer el plazo.
Véase también
- Transporte Fetch — interfaz completa de solicitudes, respuestas y señales para transportes personalizados.
- API web (WPT) — tasa de aprobación de WPT para AbortController/AbortSignal.