Skip to content

Then, Catch and Finally

You know that .then() runs when a promise succeeds. But what happens when something goes wrong? And what if you need something to run no matter what? That is what .catch() and .finally() are for.


.then() - when it works

.then() receives the fulfilled value and runs your code with it:

fetch("/patients")
  .then(response => response.json())
  .then(patients => {
    console.log("Got", patients.length, "patients")
  })

If everything goes right, .then() runs. If anything fails, .then() is skipped.


.catch() - when something goes wrong

Without .catch(), errors are silent. The page just does nothing and you have no idea why.

.catch() receives the error and lets you handle it:

fetch("/patients")
  .then(response => response.json())
  .then(patients => console.log(patients))
  .catch(error => {
    console.log("Request failed:", error)
    alert("Could not load patients. Please try again.")
  })

Common reasons .catch() runs: - No internet connection - The server is not running - The server returned an error and the JSON could not be parsed

.catch() at the end catches errors from any of the .then() calls above it.


.finally() - runs always

.finally() runs whether the promise succeeded or failed. It is useful for cleanup - like hiding a loading spinner.

// show a spinner before the request
document.getElementById("spinner").style.display = "block"

fetch("/patients")
  .then(response => response.json())
  .then(patients => console.log(patients))
  .catch(error => console.log("Error:", error))
  .finally(() => {
    // always hide the spinner when done
    document.getElementById("spinner").style.display = "none"
  })

The spinner disappears whether the request worked or not.


Side by side - with and without error handling

Without:

fetch("/patients")
  .then(response => response.json())
  .then(patients => buildTable(patients))

If this fails, you see nothing. No error, no message. The table just stays empty.

With:

fetch("/patients")
  .then(response => response.json())
  .then(patients => buildTable(patients))
  .catch(error => {
    document.getElementById("error-msg").textContent = "Failed to load. Check your connection."
  })
  .finally(() => {
    document.getElementById("spinner").style.display = "none"
  })

Now failures are visible to the user and you can act on them.


Challenge

Take this fetch call and add .catch() and .finally() to it:

fetch("https://jsonplaceholder.typicode.com/users")
  .then(response => response.json())
  .then(users => console.log("Users:", users.length))
  • .catch() should log the error message to the console
  • .finally() should log "Done" no matter what

Then test what happens when you change the URL to something that does not exist.


← The Fetch Function    Next: The Whole Picture →