In JS functions, the 'last' return wins

In JavaScript functions, which return wins?

function test() {
  return 'one';
  return 'two';
  return 'three';
}

You might say, "well it's the first one", but I'm going to try and convince you it's the last one.

Don't worry, the function above definitely returns 'one', but in this case the first return statement prevents the others from executing. The last return is return 'one', and that's the one that wins. Sure, it's also the first return, but I'm still right. [folds arms and looks smug]

I know what you're thinking, you're thinking "shut up jake", but bear with me…

Finally

finally is a thing:

function finallyTest() {
  try {
    console.log('one');
    return 'three';
  } catch (err) {
    console.log('error');
  } finally {
    console.log('two');
  }
}

console.log(finallyTest());
console.log('four');

The above logs 'one', 'two', 'three', 'four'. The finally block always runs after a try/catch, even if the try or catch return.

I hadn't used finally much in JavaScript until recently, where I find myself using it like this in async functions:

async function someAsyncThing() {
  startSpinner();

  try {
    await asyncWork();
  } catch (err) {
    if (err.name === 'AbortError') return;
    showErrorUI();
  } finally {
    stopSpinner();
  }
}

Anyway, the exciting thing about finally is it gives us the opportunity to return many times from a single function call:

function manyHappyReturns() {
  try {
    return 'one';
  } finally {
    try {
      return 'two';
    } finally {
      return 'three';
    }
  }
}

…and the result of calling manyHappyReturns() is 'three'.

The last return always wins. Not the last to appear in the function, no, that would be mad, but the last execute. The last return wins in the same way the last variable assignment wins; we don't count variable assignments that don't happen. In fact, it's spec'd very much like an assignment. The return assigns a conclusion to the function, so a return overrides a previous return. The same happens in Java and Python too. Thanks to Daniel Ehrenberg for making me aware of this little quirk!

As a side-effect, returning from finally clears a thrown error:

function catchThis() {
  try {
    throw Error('boom');
  } finally {
    return 'phew';
  }
}

The result of calling catchThis() is 'phew'.

What's the practical application of this?

There isn't one. Thanks for reading! Please never quiz folks about this in a job interview.

Bonus: Promises

Async functions behave the same as above (aside from returning a promise). However, promise.finally() behaves differently:

const promise = Promise.resolve('one').finally(() => 'two');

Here, promise fulfils with 'one'. This is probably because promise reactions are callbacks, and the caller of a callback (which is the promise in this case) has no way of telling the difference between a function that runs return undefined and one that doesn't run return at all. Since it can't mimic the finally edge case above, it just ignores it.

Although, promise.finally does impact when the promise resolves:

const wait = (ms) => new Promise((r) => setTimeout(() => r(), ms));

const promise = Promise.resolve('one').finally(async () => {
  await wait(2000);
  return 'two';
});

In this case promise still fulfils with 'one', but it takes two seconds to do so.

View this page on GitHub

Comments powered by Disqus

Jake Archibald next to a 90km sign

Hello, I’m Jake and that is my tired face. I’m a developer of sorts.

Elsewhere

Contact

Feel free to throw me an email, unless you're a recruiter, or someone trying to offer me 'sponsored content' for this site, in which case write your request on a piece of paper, and fling it out the window.