A common misconception is that finally() behaves like then().
In reality, it doesn’t receive values, ignores return values, and only affects a promise in very specific cases. Let’s see why.
finally(): Why It Can’t Change a Promise Result
finally() doesn’t receive arguments
Let's start with a quick quiz to test your understanding:
myPromise.then(() => "Hi, Sasha!")
.finally(res => console.log(res + "Whats up?"))
//what will appear in console?
If you think the answer is “Hi, Sasha!Whats up?” then keep reading 🙂
How finally() works under the hood
The finally method doesn't have its own low-level implementation in the Promise specification.
Under the hood, it simply delegates to then, using the same callback for both success and failure:
promise.finally(onDone);
// is roughly equivalent to
promise.then(onDone, onDone);
Because of this onDone does not receive any arguments (no resolved value, no rejection reason)
And what happens to the parameter value which wasn’t passed? Correct: it stays undefined.
Correct answer to the quiz is “undefinedWhats up?“ as res value is undefined.
What happens to the Promise value after finally()?
Another quiz:
myPromise.then(() => "Hi, Sasha!")
.finally(res => "my new result")
.then(res => console.log(res)) //what will be logged here?
The answer is "Hi, Sasha!"
The value returned from finally() is simply ignored.
This is a key rule: finally() doesn’t change Promise’s state.
It exists for side effects only — cleanup logic, metrics, etc.
Why the returned value from finally() is ignored
finally()doesn’t receive the resolved valueit can’t pass a new value down the chain
the Promise continues with the value from the last
then()orcatch()
So this:
.finally(() => "new value")
is effectively the same as:
.finally(() => {})
Takeaways so far
Since
finally()doesn't get any arguments, any value given to it stays undefined.The return value from a
finally()callback is ignored and doesn't change the Promise's state or the nextthen()chainThe Promise keeps the value from the last
then()orcatch()beforefinally()was called.
Can finally() change the Promise state?
Promise.resolve("OK")
.finally(() => {
throw new Error("Something went wrong");
})
.then((res) => console.log(res))
.catch((err) => console.log(err));
//what is the Promise state at this point?
//what will be logged? "OK" or "Something went wrong"?
At first glance, you might expect "OK".
After all, we already know that finally() doesn’t change a Promise’s value, right?
But in this case, the output will be “Something went wrong” and the Promise ends up rejected.
Why does this happen?
The important clarification is this:
finally()does not change a Promise’s state by default.
However, if an error is thrown inside finally() — or if it returns a rejected Promise — that error overrides the previous state.
Here is another quick quiz:
Promise.resolve("OK")
.finally(() => {
return Promise.reject("Something went wrong")
})
.then((res) => console.log(res))
.catch((err) => console.log(err));
//what is the Promise state at this point?
//what will be logged? "OK" or "Something went wrong"?
We are not throwing any error here. However, we are still getting the "Something went wrong" here.
Another way to change a Promise’s state is to return a rejected Promise from finally().
The takeaway we had previously is that the return value from a finally() callback is ignored, so why returning rejected Promise work?
Let’s investigate what happens under the hood.
Remember how finally() works?
promise.finally(onDone);
// is roughly equivalent to
promise.then(onDone, onDone);
Knowing that, let’s investigate our case:
Promise.resolve("OK")
.finally(() => {
return Promise.reject("Something went wrong")
})
.then((res) => console.log(res))
.catch((err) => console.log(err));
//is roughly the same as:
Promise.resolve("OK")
//this is our finally method
.then(
// first onDone callback
value => Promise.resolve(
(() => { return Promise.reject("Something went wrong"); })()
).then(() => value), // if resolves, continue with the previous value
// second onDone callback
reason => Promise.resolve(
(() => { return Promise.reject("Something went wrong"); })()
//throwing error
).then(() => { throw reason; }) // if gets rejected, continue with rejection reason as value
)
.then(res => console.log(res)) //this is ignored
.catch(err => console.log(err)); //this is called
Remember that throwing error changes Promise’s state?
This is what is done behind the scenes when we return a rejected Promise.
Note that if you return a new resolved promise from finally(), nothing changes. The chain waits for it to resolve, and then() continues with the original value.
Final takeaways
Since
finally()doesn't receive any arguments, any value provided to it remains undefined.The return value from a
finally()callback is ignored and does not alter the Promise's state or affect the subsequentthen()chain.-
The Promise retains the value from the last
then()orcatch()beforefinally()was invoked, unless:- The method returns a rejected Promise.
- An error is thrown within the method.
Top comments (0)