Винятки в корутинах
function validateToken(string $token): bool
{
$response = file_get_contents("https://userdirectory.example.com/api/validate?token=$token");
return json_decode($response)->valid;
}Функція validateToken з попереднього розділу має кілька проблем. Якщо UserDirectory не відповідає (тайм-аут, немає мережі, DNS не резолвиться), file_get_contents повертає false. json_decode(false) повертає null, а null->valid також null. Функція повертає щось хибне, той самий результат, що й із насправді недійсним токеном. validateToken не має способу відрізнити "токен прострочено" від "UserDirectory не відповів", хоча це дві абсолютно різні ситуації.
Правильний підхід це викидати виняток, коли сервіс не відповідає:
function validateToken(string $token): bool
{
$response = file_get_contents("https://userdirectory.example.com/api/validate?token=$token");
if ($response === false) {
throw new RemoteApiException('UserDirectory did not respond');
}
return json_decode($response)->valid;
}Але що станеться, якщо виняток буде викинуто всередині корутини? Якщо виняток викидається у звичайному коді PHP і ніхто його не ловить, PHP завершується з повідомленням Unhandled Exception. А що з корутиною?
use function Async\spawn;
spawn(function () {
throw new Exception('Something went wrong');
});
echo "Hello, world!\n";Hello, world!
Fatal error: Uncaught Exception: Something went wrong in /path/to/script.phpНа перший погляд здається, що різниці немає. Але це далеко не так. Корутини належать компоненту Scheduler, який відповідає за перемикання між ними. Кожна корутина виконується у власному логічному потоці. Якщо необроблений виняток досягає фінального обробника корутини, він зберігається в дескрипторі корутини.
$coroutine = spawn(function () {
throw new Exception('Something went wrong');
});
echo "Hello, world!\n";
sleep(1);
echo "Goodbye, world!\n";
unset($coroutine);Іншими словами, виняток не завершить програму, доки хтось усе ще утримує посилання на $coroutine. Ця логіка гарантує ідемпотентність операції await.
$coroutine = spawn(function () {
throw new Exception('Something went wrong');
});
try {
await($coroutine);
} catch (Exception $e) {
echo "Caught the exception: {$e->getMessage()}\n";
}
try {
await($coroutine);
} catch (Exception $e) {
echo "Caught the exception a second time: {$e->getMessage()}\n";
}Повторні виклики await для тієї самої корутини дають однакову поведінку, незалежно від того, чи корутина все ще виконується, чи ні. Це дозволяє поводитися з корутиною як із Future, обіцянкою результату, який можна отримати колись у майбутньому. Якщо результат уже існує, await повертає його негайно.
Тепер ми можемо покращити код, який перевіряє токен та оновлює профіль, додавши обробку винятків:
$validation = spawn(validateToken(...), $token);
try {
if (profileExists($userId) && await($validation)) {
updateProfile($userId, $changes);
}
} catch (RemoteApiException $e) {
// Можливо, варто повторити операцію пізніше?
}Важливо! Після виклику
awaitабо будь-якої іншої операціїawait_*корутина позначається як оброблена, і її виняток більше не спричинить завершення програми PHP.