DEV Community

Cover image for Finally in Promises & Try/Catch
Anna Rankin
Anna Rankin

Posted on

Finally in Promises & Try/Catch

Lately, I've been experimenting more with the async/await keywords in JavaScript. I noticed that I sometimes struggle to reconcile the strategies I use with Promises with the way I need to write code in the newer syntax. Most recently, I was playing around with finally in some try/catch blocks and came upon some behavior I didn't expect.

This post assumes a general understanding of how asynchronous JavaScript code works - particularly, how Promises work. (If you're looking for an in-depth explanation of async JS from callbacks to the async/await keywords, there's a pretty good overview on javascript.info - you can also check out Mostafa Gaafar's article for some of the neat features of async/await.)

For context - in the JavaScript codebase I spend a lot of my time in, we've historically dealt with asynchronous actions by using Promises heavily. Generally, this pattern is a lot more familiar to me:

const loadSomething = () => {
  return fetchSomeData()
    .then(data => doSomethingWith(data))
    .catch(error => logAndReport(error))
}
Enter fullscreen mode Exit fullscreen mode

And this is less familiar:

const loadSomething = async () => {
  try {
    const data = await fetchSomeData()
    return doSomethingWith(data)
  } catch (error) {
    logAndReport(error)
  }
}
Enter fullscreen mode Exit fullscreen mode

finally...?

You'll notice that a finally callback/block is missing from both of the examples above. I don't use either often in my code, which led me to a misunderstanding (of both, really). Let's dive into the differences between this concept in Promises and in try/catch!

finally in Promises

When you use the somePromise.then(x).catch(y).finally(z) pattern, your business logic is generally happening in the then callback (x, above - what you want to do once somePromise has resolved) or in the catch callback (y above - returns what you want to pass along in case something goes horribly wrong). You might never have even used finally in your code - and that's fine.

According to the MDN docs, a finally callback allows you to execute logic once your Promise has been settled - resolved or rejected - one way or the other. It has absolutely no impact on the value that your promise will resolve to - it doesn't even have access to it. In fact, the documentation states that:

Promise.resolve(2).finally(() => {}) will be resolved with 2.

This means (somewhat counterintuitively) you can sprinkle finally callbacks liberally throughout your promise chain without changing the final result that it will resolve to:

// Please don't do this πŸ˜…

Promise.resolve({ some: 'data' })
  .finally(() => { console.log('WHALE HELLO THERE πŸ‹') })
  .then(data => ({ ...data, anAdditional: 'key'  }))
  .finally(() => { console.log('Looks like we made it past the first step πŸ™') })
  .then(data => ({ ...data, yetAnother: 'thing added' }))
  .finally(() => { console.log("We're done I think πŸ™Œ") })
  .then(data => {
    console.log('Final result:', data)
  })
Enter fullscreen mode Exit fullscreen mode

If you run this code, you should see this:

result of executing the above code: Each step is logged to the console sequentially; the final result is an object containing three keys

finally in try/catch blocks

The try/catch/finally pattern has been around for a long time in JavaScript - since version 1.4 (ES3 specification, around 1999). There are a couple of logical parallels I'd drawn between this pattern and how promises are handled:

try/then:
This is where our "happy path" logic goes - if nothing breaks, all the action happens here!

catch:
This is where we end up when things go wrong, and gives us a chance to redeem ourselves πŸ™

finally:
This logic will execute after the try/then (and possibly catch) logic has completed. This code runs no matter what, whether we've encountered an error or not.

The difference here that tripped me up is related to return statements. If your finally block does not include a return statement, it has no effect on the return value. However, if you return a value from a finally block, that value will override all other returns and be the final result of your function. (Check out this example from the docs!)

// This worked as I expected.
const returnFromTryCatch = (someFunction) => {
  try {
    return someFunction()
  } catch (error) {
    return `Caught an error: ${error}`
  } finally {
    // This block has no effect on the return value.
    console.log('All done!')
  }
}

// This was a surprise to me!
const returnFromFinally = (someFunction) => {
  try {
    return someFunction()
  } catch (error) {
    return `Caught an error: ${error}`
  } finally {
    // Wait... so I'm just swallowing my return and error handling?
    return 'All done!'
  }
}
Enter fullscreen mode Exit fullscreen mode

This makes sense, but it felt inconsistent to me. My experience with Promises reared its head - why should a finally block ever be allowed to override the value a function returns?

pixel art of a woman looking at her laptop in deep thought

Finding the Reason

Finally, I pinged my tech lead detailing my annoyance, and he sent me a link to a related StackOverflow discussion. Seeing the ECMAScript specification (emphasis mine) for this behavior helped it settle into place in my brain:

The production TryStatement : try Block Finally is evaluated as follows:

Let B be the result of evaluating Block.
Let F be the result of evaluating Finally.
If F.type is normal, return B.
Return F.

(It's worth noting the "completion types" according to the ECMAScript spec are "One of normal, break, continue, return, or throw" - I have assumed that a function that does not include a break, continue, return, or throw keyword qualifies as "normal." Kind of weird semantics there.)

Note on Multiple Return Statements

The code samples in this post do not utilize a single return. I'm not going to get too far into the debate around multiple return statements - I will say that in general, having a single return for longer functions has served me well in the past, but I've found them less helpful in shorter blocks. It probably would have made my life easier in this case, though!

Discussion (26)

Collapse
ben profile image
Ben Halpern

Finally, I pinged my tech lead detailing my annoyance

Collapse
alainvanhout profile image
Alain Van Hout

Good catch!

Collapse
functionalstoic profile image
JasonSooter

You gave it the good old college try with that one!

Collapse
annarankin profile image
Anna Rankin Author

😜heeheehee

Collapse
frankrobert profile image
Frank Robert

Great write-up! I've only ever found a single good use for finally. You could do performance checks using console.time.

// logs async evaluation in ms
const promises = [new Promise(), new Promise()];

for (const promise of promises) {
  console.time('timer');
  try {
    await promise();
  } catch(error) {
    console.error(error);
  } finally {
    console.timeEnd('timer');
  }
}
Collapse
mcstepp profile image
Meg Stepp

I also use finally to toggle loading indicators to keep things DRY in my then/catches. If I'm doing the same line of code in both, I move it to the finally block.

Collapse
functionalstoic profile image
JasonSooter

Nice tip!

Collapse
thejohnstew profile image
John Stewart

Such a well written article and I learned something new!

Collapse
annarankin profile image
Anna Rankin Author

Daww thank you! 😁 Love that gif

Collapse
annarankin profile image
Anna Rankin Author

It's a neat tool - and it's fun what you can learn when you try weird things lol. One thing I will note is that you might run into difficulty using Promise.finally in applications that are tested with Jest.

Collapse
kurisutofu profile image
kurisutofu

When I saw the code for try-catch-finally, my first thought was that you should not have a "return" in the try and catch if you are using finally.
My understanding was that finally has the "final say".

I find interesting the way promises handle that. Which I think makes sense too ... each action "block" would basically be a local try-catch-finally except the "catches" are grouped at the end for handling.

Collapse
annarankin profile image
Anna Rankin Author

Sorry I missed your comment - I agree, the multiple returns with the catch block are pretty wacky. The way Promises work makes sense looking at it now, but my brain totally rebelled on first look 😡Thanks kuristofu!

Collapse
sagar profile image
Sagar • Edited

Finally, Anna wonderful article.

Collapse
annarankin profile image
Anna Rankin Author

Thank you Sagar! πŸ™Œ

Collapse
alifarag_90 profile image
AliFarag

Good article and great effort.

Collapse
annarankin profile image
Anna Rankin Author

Thank you Ali! πŸ™Œ

Collapse
aelafnstein profile image
Aelaf

Thank you!!! Promise I will use promise!!!

Collapse
annarankin profile image
Anna Rankin Author

I'm sorry I missed your comment! Glad you liked it 😊

Collapse
rhymes profile image
rhymes

Thanks Anna, it's quite different on how finally works in contrast to other general purpose languages. Nice to know :-) Hopefully 99% of the time people will use finally just to cleanup...

Collapse
annarankin profile image
Anna Rankin Author

Thank you! And agreed πŸ™

Side note, the history of decisions that go into defining how a language should work are fascinating 😡

Collapse
rhymes profile image
rhymes

It definitely is!

Collapse
cdsaenz profile image
Charly S.

Beautiful article Thanks a lot.

Collapse
annarankin profile image
Anna Rankin Author

Thank you Charly! 😊

Collapse
diegomgar profile image
Dieg Oto

I am using finally in try/catch almost forever, but Ty your post I am aware of the the finally return feature. Good post!

Collapse
revel109 profile image
Abu Yusuf

I would like work with my error.response/error.response.status in catch.
How to do that?