Promise.finally is the one everyone wrote by hand

Turning off a loading spinner has to happen on both the success and the failure path, so it got duplicated into then and catch, and one of the two eventually drifted.

// before
fetch(url)
  .then(r => { setLoading(false); return r.json(); })
  .catch(e => { setLoading(false); report(e); });

// ES2018
fetch(url)
  .then(r => r.json())
  .catch(report)
  .finally(() => setLoading(false));

The callback receives no arguments, deliberately — it is for cleanup, not for inspecting the outcome. It also passes the value or rejection through unchanged unless it throws itself, so it can be inserted anywhere in a chain without altering what comes after. That transparency is the property that makes it composable, and it is the reason the hand-written version was usually subtly wrong.