Modèles d'utilisation d'AbortController
AbortController et AbortSignal sont des implémentations WHATWG complètes dans Phasis. Les méthodes standard de composition (AbortSignal.any, AbortSignal.timeout) sont également fournies. Cette page présente les modèles d'utilisation courants et la propagation de l'annulation à travers la frontière PHP.
Annuler une requête fetch
const ctrl = new AbortController();
const p = fetch(url, { signal: ctrl.signal });
ctrl.abort(); // p rejects with AbortError DOMException
Par défaut, le motif est AbortError, mais vous pouvez fournir le vôtre :
ctrl.abort(new Error('user cancelled'));
fetch rejette avec la valeur que vous avez transmise.
Délai d'attente
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
AbortSignal.timeout(ms) renvoie un signal qui s'annule de lui-même après ms. Le minuteur se déclenche via la boucle d'événements du moteur, la même que celle qui pilote setTimeout / queueMicrotask (voir API : boucle d'événements). Pendant le transfert, le transport fetch interroge le signal — pour le CurlTransport par défaut, via CURLOPT_PROGRESSFUNCTION — afin que la requête soit rapidement annulée dès que l'échéance est atteinte.
La boucle utilise par défaut le temps virtuel : elle fait avancer son horloge jusqu’à la prochaine échéance en attente au lieu d’attendre PHP. Ainsi, un fetch(url, { signal: AbortSignal.timeout(5000) }) se termine avant l’échéance ou est interrompu au prochain cycle de boucle — il ne fait jamais attendre PHP. Pour un rythme en temps réel, l’hôte doit piloter tickEventLoop() en fonction de l’horloge réelle.
Course : la première annulation l’emporte
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
Propagation à travers les couches
Encapsulez votre propre travail asynchrone dans une boucle qui tient compte du signal :
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 requête fetch hérite directement du signal ; le if (signal.aborted) manuel couvre les intervalles entre les requêtes, pendant lesquels la boucle de page continuerait sinon à s’exécuter.
Écouter l’événement d’interruption
ctrl.signal.addEventListener('abort', () => {
releaseDatabaseLock();
cancelDownstreamWork();
});
signal.reason est défini avant le déclenchement des écouteurs.
Signal dans un transport fetch personnalisé
Si vous remplacez le transport via setFetchTransport(), le signal est transmis à votre fonction sous la forme d’un JsObject. Respectez-le en vérifiant régulièrement son état :
$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',
);
}
// ...
});
Lancer TransportException avec kind: 'aborted' fait rejeter le JS côté fetch avec AbortError DOMException — comme si la boucle d’interruption de la spécification avait été exécutée.
Pour les backends prenant en charge l’annulation nativement (le on_progress de Guzzle, le ResponseInterface::cancel de Symfony), utilisez le même schéma de vérification et de lancement d’exception.
Modèles qui ne fonctionnent pas encore
signal.addEventListener('abort', () => fetch(cleanupUrl))— les écouteurs se déclenchent de façon synchrone ; la requête fetch de nettoyage démarre, mais ne peut pas se terminer pendant la propagation de l’interruption. Planifiez-la via leawaitenvironnant.
setTimeout(() => ctrl.abort(), N) fonctionne (la famille des minuteurs est disponible et couverte par WPT), mais AbortSignal.timeout(N) est plus court et exprime mieux l’intention lorsque vous voulez simplement un signal d’interruption après une échéance.
Voir aussi
- Transport Fetch — interface complète de requête, réponse et signal pour les transports personnalisés.
- API Web (WPT) — taux de réussite WPT d’AbortController/AbortSignal.