Promise.Finally… Finally

Most languages that I’ve worked with have an additional tool for promises that covers items that we have to do at the end of a task, regardless of whether it succeeds or not.

Using this example we’ll explore uses for finally and compare with using async / await for the same task.

fetchAndDisplay takes two parameters, a URL and the element you want to insert the content into.

The original version does the following:

  • shows a loading spinner to indiccate the content is loading
  • Fetches the URL
  • If the fetch succeeds
    • Insert the text into the element
    • Hide the loading spinner
  • If it fails
    • Insert an error message into the text element
    • Hide the loading spinner

This is a short example but it shows one of the reasons why we need a finally block. the hideLoadingSpinner function is called in muliple locations of fetchAndDisplay. In this case it’s simple but you can imagine the potential damage if we were cleaning data after we complete a large transaction, whether success or failure.

const fetchAndDisplay = ({ url, element }) => {
  showLoadingSpinner();
  fetch(url)
    .then((response) => response.text())
    .then((text) => {
      element.textContent = text;
      hideLoadingSpinner();
    })
    .catch((error) => {
      element.textContent = error.message;
      hideLoadingSpinner();
    });
  };

fetchAndDisplay({
  url: someUrl,
  element: document.querySelector('#output')
});

The finally block takes care of the code dupplication. It will run once whether the promise fulfills or rejects. Since we want to hide the spinner regardless of whether it succeeds or fails we put it in the finally block. The code now looks like this.

const fetchAndDisplay = ({ url, element }) => {
  showLoadingSpinner();
  fetch(url)
    .then((response) => response.text())
    .then((text) => {
      element.textContent = text;
    })
    .catch((error) => {
      element.textContent = error.message;
    })
    .finally(() => {
      hideLoadingSpinner();
    });
};

We can also use the async/await to do the same thing with the full try/catch/finally blocks; taking into account that we still want to use hideLoadingSpinner only once.

const fetchAndDisplay = async ({ url, element }) => {
  showLoadingSpinner();
  try {
    const response = await fetch(url);
    const text = await response.text();
    element.textContent = text;
  } catch (error) {
    element.textContent = error.message;
  } finally {
    hideLoadingSpinner();
  }
};