DEV Community

Cover image for Why `finally` Runs Before `return` Takes Effect
Byte Corner
Byte Corner

Posted on Originally published at bytecorner.dev

Why `finally` Runs Before `return` Takes Effect

Why Your finally Runs After return

A return looks like the end of a function.

But put a finally after it:

function getValue() {
  try {
    return 42;
  } finally {
    console.log("cleanup");
  }
}

console.log(getValue());
Enter fullscreen mode Exit fullscreen mode

The output is:

cleanup
42
Enter fullscreen mode Exit fullscreen mode

The return happened.

The function just hadn't finished leaving yet.

🔴 The Problem

It's tempting to think of return as:

return
  ↓
function stops immediately
Enter fullscreen mode Exit fullscreen mode

But with finally, there's another step.

When JavaScript reaches:

return 42;
Enter fullscreen mode Exit fullscreen mode

it evaluates 42 and prepares to return it.

Before the function actually completes, JavaScript runs the finally
block.

So the flow is roughly:

return 42
    ↓
remember 42
    ↓
run finally
    ↓
finish returning 42
Enter fullscreen mode Exit fullscreen mode

The important part is that the return value has already been determined.

finally just gets to run before the function actually exits.

🟢 The Solution

This is exactly what makes finally useful for cleanup.

Imagine something that needs to be closed regardless of what happens:

function readFile(file) {
  try {
    return file.read();
  } finally {
    file.close();
  }
}
Enter fullscreen mode Exit fullscreen mode

file.read() can still determine the value returned to the caller.

But file.close() runs before the function actually leaves.

Without finally, you'd have to duplicate the cleanup:

function readFile(file) {
  try {
    const result = file.read();

    file.close();

    return result;
  } catch (error) {
    file.close();

    throw error;
  }
}
Enter fullscreen mode Exit fullscreen mode

With finally, the cleanup has one place:

function readFile(file) {
  try {
    return file.read();
  } finally {
    file.close();
  }
Enter fullscreen mode Exit fullscreen mode

Whether try returns, throws, or finishes normally, finally gets its
chance to run.

âš¡ The Dangerous Part

finally isn't limited to cleanup.

It can actually change how the function finishes.

function getValue() {
  try {
    return 42;
  } finally {
    return 100;
  }
}

console.log(getValue());
Enter fullscreen mode Exit fullscreen mode

The result is:

100
Enter fullscreen mode Exit fullscreen mode

The original return 42 prepared the function to return 42.

Then finally executed another return.

That new return becomes the function's actual result.

The same thing can suppress an error:

function getValue() {
  try {
    throw new Error("Boom");
  } finally {
    return 42;
  }
}

console.log(getValue());
Enter fullscreen mode Exit fullscreen mode

Instead of throwing, the function returns:

42
Enter fullscreen mode Exit fullscreen mode

The return inside finally replaced the pending exception.

That's why returning from finally is usually a bad idea.

You're no longer just cleaning up.

You're changing the outcome.

🧠 The Takeaway

Don't think of:

return value;
Enter fullscreen mode Exit fullscreen mode

as:

"I am completely done now."
Enter fullscreen mode Exit fullscreen mode

Think of it as:

"I know how I want to finish."
        ↓
     finally
        ↓
"I actually leave now."
Enter fullscreen mode Exit fullscreen mode

So this is perfectly reasonable:

try {
  return result;
} finally {
  cleanup();
}
Enter fullscreen mode Exit fullscreen mode

But this deserves suspicion:

try {
  return result;
} finally {
  return somethingElse;
}
Enter fullscreen mode Exit fullscreen mode

return decides the result.\
finally gets the last chance to change how the function exits.

Top comments (0)