Проблема с new URL(), и как URL.parse() её решает

Источник: «The problem with new URL(), and how URL.parse() fixes that»
Часто приходится анализировать множество URL-адресов. Частично для их проверки, а также для нормализации или извлечения определённых частей из URL. API URL в браузерах позволяет это делать, но его эргономика не идеальна.

Проблема с new URL()

Слово «new» в строке new URL() указывает на то, что он используется как конструктор: вызывая его, создаётся новый экземпляр URL. Однако, если передать неверно сформированный URL, который он не сможет разобрать, он выбросит ошибку. Поскольку он выдаёт ошибку, необходимо написать код для её обработки.

Если этого не сделать, то выброшенная ошибка не будет обработана, и JS перестанет выполняться. Следующий код выглядит отлично, но если urlstring неправильно сформирована, он остановит выполнение:

const urlstring = "this is not a URL";
const not_a_url = new URL(urlstring);
// Uncaught TypeError: URL constructor: "this is not a URL" is not a valid URL.

Поэтому необходимо обернуть его в try...catch, чтобы ошибка была поймана.

const urlstring = "this is not a URL";
let not_a_url;

try {
not_a_url = new URL(urlstring);
} catch {
// мы поймаем и проигнорируем ошибку
// not_a_url уже undefined, поэтому не нужно ничего делать.
}

Это намного больше строк кода, больше визуального шума и значит, что придётся изменить not_a_url с const на let, чтобы получить возможность перезаписи. В итоге поток управления приложением становится более сложным.

Делаем немного лучше

Недавно в URL API была добавлена функция URL.canParse(), возвращающая true, если URL-адрес можно разобрать.

Она доступна для кросс-браузерного использования только с декабря 2023 года, поэтому, возможно, её рано повсеместно использовать, но она делает код более читабельным.

Вместо того чтобы пытаться поймать ошибку, сначала можно проверить, можно ли разобрать URL, прежде чем разбирать его, и сделаем это одной строкой:

const urlstring = "this is not a URL";

const not_a_url = URL.canParse(urlstring) && new URL(urlstring);
// not_a_url = false

Таким образом, not_a_url снова становится const, что, безусловно, проще для понимания.

Жалуемся

Вместо проявления конструктивности и написания собственной маленькой функции для абстрагирования этих try...catch или canParse от своей обычной кодовой базы, я решил поступить правильно и пожаловаться в Twitter:

Заставить new URL() выбрасывать ошибку, когда передаёте ей недопустимый URL, было ужасным выбором API.

Немного позже Anne van Kesteren ответила ссылкой на GitHub issue, в нём обсуждалось добавление функции «parse» к URL, которая бы не выбрасывала ошибку.

Anne добавила этот issue в 2018 году, но мой твит возобновил интерес. Немного позже Anne добавила URL.parse() в спецификацию, и ошибки в реализации были зафиксированы для всех браузерных движков.

Anne самостоятельно реализовала его в WebKit, и он также будет поставляться в Chromium 126 и Firefox 126.

Использование URL.parse()

С помощью URL.parse() можно вернуться к исходному примеру, приведённому выше, и сохранить поток управления как можно более простым:

const urlstring = "this is not a URL";

const not_a_url = URL.parse(urlstring);
// not_a_url = null

Браузеры с этой функцией появятся в ближайшие несколько месяцев (Firefox — в мае, Chrome — в июне, пока не удалось выяснить, когда появится Safari), так что придётся немного подождать, прежде чем использовать эту функцию. Не могу дождаться, когда избавлюсь от всех своих try...catch вызовов!

Дополнительные материалы

Предыдущая Статья

Новое в Symfony 7.1: Атрибут IsCsrfTokenValid

Следующая Статья

Больше способов инстанцирования веб-компонентов