As someone building a browser I need to parse a lot of URLs. Partially to validate them, but also to normalize them or get specific parts out of the URL. The URL API in browsers lets you do that, but it’s ergonomics aren’t ideal.
The problem with new URL()
The “new” in front of new URL() indicates that it’s used as a constructor: calling it creates a new URL instance for you. When you give it a malformed URL however, one that it can’t parse, it throws an error. Because it throws an error, you need to write code to handle that error.
If you don’t do that, The thrown error won’t get handled and your JS stops being executed. The following code looks great but if urlstring
is malformed, it will stop execution:
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.
So you'll want to wrap it in a try...catch so that the error is caught.
const urlstring = "this is not a URL";
let not_a_url;
try {
not_a_url = new URL(urlstring);
} catch {
// we catch and ignore the error
// not_a_url is already undefined so no need to actually do anything.
}
That's a lot more lines of code, has more visual noise and it means you have to change not_a_url
from a const
to a let
to be able to overwrite it. The control flow of the application ends up being more complex.
Making it slightly better
A recent addition to the URL api is URL.canParse()
, a function that returns true if the URL is a parseable URL.
It's only been available cross-browser since December 2023 so it might be a little too early for general use, but it does make the code more readable.
Instead of trying and catching the error, we can first check if the URL is parseable before parsing it, and we can do that inline:
const urlstring = "this is not a URL";
const not_a_url = URL.canParse(urlstring) && new URL(urlstring);
// not_a_url = false
This makes not_a_url
a const again, and is definitely easier to understand.
Complaining
Rather than being constructive and writing my own little function to abstract that try...catch or canParse away from my regular code base, I decided to do the right thing and complain on Twitter:
Making new URL() throw when you give it an invalid URL was a terrible API choice.
Not much later Anne van Kesteren replied with a link to a GitHub issue discussing the addition of a "parse" function to URL that would not throw.
Anne added that issue in 2018 but my tweet renewed interest. Not much later, Anne added URL.parse()
to the spec and implementation bugs were filed for all browser engines.
Anne himself implemented it in WebKit and it's also going to ship in Chromium 126 and Firefox 126.
Using URL.parse
With URL.parse we can go back to that original example all the way up top, and keep our control flow as simple as possible:
const urlstring = "this is not a URL";
const not_a_url = URL.parse(urlstring);
// not_a_url = null
The browsers with this feature will ship in the next few months (Firefox in May, Chrome in June, I've not been able to figure out when Safari will) so you'll have to wait a little before using it but I can't wait to get rid of all my try..catch calls!
The post The problem with new URL(), and how URL.parse() fixes that first appeared on Kilian Valkhof.