Login forms often try and hide whether you guessed a valid username or not by reporting invalid username and/or password.
If your backend fetches the user by username and then compares the password, a timing attack can be used to tell whether an account even exists by seeing if the 'compare returned password hashes' check is performed.
This is noticeable on sites (that I have developed at least) that use bcrypt which purposefully takes a moment to hash and then validate the password.
In order to not reveal the username exists (by returning the 'invalid login' error quickly), I have a bcrypt compare operation performed on a hard-coded 'Ignored Password' when no valid user exists, so that the 'compare password hashes' cost is always paid.
There's usually an easier way to check if a username exists - skip the login form and try the signup form. If you can't sign up with that name because its taken, then it exists :)
Hence, signup forms tend to have CAPTCHAS more often than login forms. Also, it's easier to insert an artificial per-signup-form submission delay under the guise of "setting up your account". An artificial delay upon login might be annoying, but during signup it's not as painful (seeing as it's a one-time thing).
I would have optimized your last paragraph - "Why don't you hash the given password, and then check for the user, and then their password hash?" and then realized I was in error.
The time taken to hash or not is definitely noticeable, but unless you take steps to avoid it, even the string comparison of hashes can leak timing information.
Edit: As noted in an earlier comment, below. Whoops.
Since bcrypt uses a random salt, the approach that I think you are suggesting (hash the input pass, check the user, and then strcmp the hashed pass in the database with the hashed pass from user input) would not work. Try bcrypting the same string repeatedly and you'll see that the same string produces different output each time.
If your backend fetches the user by username and then compares the password, a timing attack can be used to tell whether an account even exists by seeing if the 'compare returned password hashes' check is performed.
This is noticeable on sites (that I have developed at least) that use bcrypt which purposefully takes a moment to hash and then validate the password.
In order to not reveal the username exists (by returning the 'invalid login' error quickly), I have a bcrypt compare operation performed on a hard-coded 'Ignored Password' when no valid user exists, so that the 'compare password hashes' cost is always paid.