Начини за работа с AbortController
AbortController и AbortSignal са пълни реализации на WHATWG в Phasis. Включени са и стандартните помощни функции за композиция (AbortSignal.any, AbortSignal.timeout). Тази страница разглежда практическите начини за употреба и разпространението на отмяната през границата на PHP.
Отмяна на fetch
const ctrl = new AbortController();
const p = fetch(url, { signal: ctrl.signal });
ctrl.abort(); // p rejects with AbortError DOMException
По подразбиране причината е AbortError, но можете да зададете своя:
ctrl.abort(new Error('user cancelled'));
fetch отхвърля промиса с подадената от вас стойност.
Изтичане на времето
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
AbortSignal.timeout(ms) връща сигнал, който прекратява действието си след ms. Таймерът се задейства от цикъла на събитията на енджина, същия, който задвижва setTimeout / queueMicrotask (вижте API: Цикъл на събитията). Транспортът за fetch проверява сигнала по време на прехвърлянето — за транспорта по подразбиране CurlTransport чрез CURLOPT_PROGRESSFUNCTION — така че заявката се прекратява бързо, щом настъпи крайният срок.
По подразбиране цикълът работи с виртуално време: часовникът му прескача до следващия чакащ краен срок, вместо да изчаква PHP, така че fetch(url, { signal: AbortSignal.timeout(5000) }) или приключва преди крайния срок, или се прекратява при следващата итерация на цикъла — никога не кара PHP да чака. За работа в реално време хостът трябва да задвижва tickEventLoop() спрямо часовника.
Надпревара: първата отмяна печели
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
Разпространение през слоевете
Увийте собствената си асинхронна работа в цикъл, който отчита сигнала:
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 наследява сигнала директно; ръчният if (signal.aborted) покрива паузите между заявките, през които цикълът на страницата иначе би продължил да работи.
Прослушване на събитието за отмяна
ctrl.signal.addEventListener('abort', () => {
releaseDatabaseLock();
cancelDownstreamWork();
});
signal.reason се задава преди извикването на слушателите.
Сигнал в потребителски транспорт за fetch
Ако смените транспорта чрез setFetchTransport(), сигналът се подава на вашата извикваема функция като JsObject. Проверявайте го, за да спазите отмяната:
$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',
);
}
// ...
});
Хвърлянето на TransportException с kind: 'aborted' кара страната JS на fetch да отхвърли с AbortError DOMException — все едно е изпълнен цикълът за прекъсване от спецификацията.
За бекендове с вградена поддръжка на отмяна (Guzzle на on_progress, Symfony на ResponseInterface::cancel) свържете същата схема за проверка и хвърляне на грешка.
Начини на употреба, които все още не работят
signal.addEventListener('abort', () => fetch(cleanupUrl))— извикването на слушателите е синхронно; заявката за почистване стартира, но няма да завърши по време на извикването за отмяна. Планирайте я чрез обграждащияawait.
setTimeout(() => ctrl.abort(), N) работи (семейството таймери е включено и покрито от WPT), но AbortSignal.timeout(N) е по-кратко и изразява по-добре намерението, когато искате само сигнал за прекратяване след краен срок.
Вижте също
- Транспорт за fetch — пълен интерфейс за заявка/отговор/сигнал за потребителски транспорт.
- Уеб API (WPT) — процент на успешно преминалите WPT тестове за AbortController/AbortSignal.