В PHP, начиная с версии 5.5, а в JavaScript с редакции 1.4 есть поддержка обработки с исключений с помощью конструкций try…catch…finally.
Для многих остается загадкой, в чем же функция этого «finally«. В документации написано, что код в блоке finally будет выполнен независимо от того, было ли вызвано исключение в блоке try. Напомню, что код в блоке try выполняется до конца блока или до тех пор, пока не будет вызвано исключение, а в этом случае выполняется код в блоке catch. То есть, на первый взгляд блок finally избыточен, ведь если мы хотим, чтобы код выполнился в любом случае после try и catch, достаточно просто разместить этот код после них. Во многих случаях это действительно так, но есть несколько случаев, когда finally может быть полезен.
Важно пояснить, что finally выполнится в любом случае перед выходом из конструкции try/catch/finally, то есть это пригодится для обработки ситуаций, когда блок catch не отработал, а исключение возникло. Например, если блока catch нет вообще или в подобной ситуации, когда ожидается исключение определенного вида, а вызывается другое:
try { echo "В try вызвано исключение\n"; throw new StrangeException(); } catch (SomeException $e) { echo "Вызвано исключение SomeException \n"; } catch (AnotherException $e) { echo "Вызвано исключение AnotherException \n"; } finally { echo "Этот блок кода выполнится всегда\n"; }
То есть мы можем как-то среагировать на такую кажущуюся «неуправляемой» ситуацию перед тем, как исключение уйдет на верхний уровень.
В случае вложенных исключений, если блоки finally в них присутствуют, то код будет выполнен во всех таких блоках последовательно, начиная с самого «глубокого» уровня. Например:
function canThrowException() { try { echo "В try вызвано исключение\n"; throw new StrangeException("Что-то случилось!"); } catch (SomeException $e) { echo "Exception: " . $e->getMessage() . "\n"; } finally { echo "Этот блок в функции canThrowException отработает сначала!\n"; } } try { canThrowException(); } catch (StrangeException $e) { echo "Внешнее исключение: " . $e->getMessage() . "\n"; } finally { echo "Это блок кода также отработает, но в конце\n"; }
Блоки finally могут быть полезны при соединении с базой данных или со сторонним API, когда, например, у вас может быть многоэтапная обработка исключений, но при этом важно при любом исходе закрыть соединение или какой-то ресурс — такие процедуры безусловного «завершения» или «очистки» как раз удобно размещать в finally.
Особое внимание при работе с try/catch/finally надо уделить оператору return внутри блоков. Поскольку finally выполнится в любом случае до выхода из try/catch, то любые return в этих блоках выполнятся тоже только после finally, и если finally также будет содержать return, то окончательно возвращаемое значение будет именно тем, что указано после return в finally. При этом, если выход из try/catch/finally произойдет по return, то исключение не передастся дальше, а продолжится нормальный ход выполнения программы, как если бы исключение было успешно обработано.
Как уже было отмечено, в finally можно поместить операции «завершения» чего-либо. Например, это очень удобно делать для закрытия файловых потоков или «подчистку» после обращений к внешним API.
Например, для закрытия потоков хороший пример написан в статье Frank de Jonge:
Код без с использования finally:
function flupper($location) { $handle = fopen($location, 'r+'); $firstLine = fgets($handle, 1024); if ($firstLine === false) { fclose($firstLine); return false; } rewind($handle); $uppedFirstLine = strtoupper($firstLine); if (false === fwrite($handle, $uppedFirstLine)) { fclose($firstLine); return false; } fclose($firstLine); return $uppedFirstLine; }
Код с использованием finally:
function flupper($location) { try { $handle = fopen($location, 'r+'); $firstLine = fgets($handle, 1024); if ($firstLine === false) { return false; } rewind($handle); $uppedFirstLine = strtoupper($firstLine); if (false === fwrite($handle, $uppedFirstLine)) { return false; } return $uppedFirstLine; } finally { fclose($handle); } }
То есть мы избавились от копипасты закрытия потока, и проводим эту рутинную операцию в одном месте.
Вдогонку… Понял. Блок catch (\Throwable $e) исполнится только при условии выброса исключения. А finally — в любом случае.
А вариант написать нужный нам код просто под низом конструкции try/catch не будет выполняться, если было выброшено исключение. Таким образом, если нам нужно что-то сделать как при успешном выполнении try-кода, так и при выбросе исключения, единственный выход, чтобы сделать это красиво — блок finally.
как-то так, да )
«то есть это пригодится для обработки ситуаций, когда блок catch не отработал, а исключение возникло»
Почему нельзя finally заменить на catch (\Throwable $e) ? Который точно подхватит все возможно оставшиеся не перехваченными исключения в предыдущих блоках catch.
finally отрабатывает даже если исключения не было