try-catch-FAIL

Failure is inevitable

NAVIGATION - SEARCH

Practical Promises in JavaScript - Finally

Welcome to yet another entry in my Practical Promises series! We're nearing the end, but I overlooked one important, and useful, capability of several promise libraries, which is what we'll talk about today!

For Those Just Tuning In...

If you are just joining us, here is what you have missed so far:

In part 1, we talked about what promises are and what they can be used for.

In part 2, we started looking at how we can create promises.

Then in part 3, we saw how each call to then actually makes a new promise, and that those promises can be chained together.

In part 4, we learned how to combine promise chaining with the creation of new promises in order to simplify complex async workflows.

In part 5, we applied everything we have learned so far to create a nice, clean API that unwraps a complex result object via promise chaining.

In part 6, we explored what happens to our promises when we call then and catch in different orders.

Are we finally done yet?

We know that promises can represent a value that might be available at some point in the future...

const promise = Promise.resolve(1);

And we know that we can register a callback to be executed when/if a value becomes available using then....

promise.then(x => console.log(`Got a value: ${x}`));

And we also know that we can register a callback to be executed if something goes wrong, and the value will never be available:

promise.catch(err => console.log(`There was a problem: ${err}`));

The then and catch instance functions are all that standard ES6/ES2015 promises provide. However, many common JavaScript promise libraries expose an additional function called finally. This is true of AngularJS's $q service, q itself, BlueBird, and probably others, too.

As you might expect, finally in these libraries allows us to register a callback that is executed if the promise is resolved or rejected. The usage is pretty straight forward:

Note: If you want to follow along, you'll need to make sure you have Q loaded up from https://cdnjs.cloudflare.com/ajax/libs/q.js/1.5.0/q.min.js. If you want to follow along your browser's JavaScript console, you can execute this: const e = document.createElement('script'); e.src = 'https://cdnjs.cloudflare.com/ajax/libs/q.js/1.5.0/q.min.js'; document.body.appendChild(e);

const resolvedPromise = Q.resolve('resolved!');
promise.finally(() => console.log(`We are finally here!`));

const rejectedPromise = Q.reject('rejected!');
promise.finally(() => console.log(`We still got here!`));

In my experience, finally is typically used to perform cleanup after an async action completes. For example, you might want to clear a "loading" flag once an API call to retrieve data has completed, even if that call fails.

Emulating finally in ES6

Unfortunately, standard ES6 promises do not expose a finally function. The good news is that we can sort of emulate one by leveraging promise chaining.

As we've learned, calling catch actually creates a new promise. That new promise is actually resolved with whatever our catch returns (or undefined if our catch doesn't return anything). So, we can achieve the same then-catch-finally pattern by doing this:

const promise = Promise.reject('rejected!');
promise.then(() => console.log('This will never be executed...'))
       .catch(() => console.log('We are in our catch...'))
       .then(() => console.log('And now we are in our second then, which is acting like a finally...'));
Output:
We are in our catch...
And now we are in our second then, which is acting like a finally...

It isn't as easy-to-read as finally, but it does work.

finally is NOT Standard!

The important take away from this post is that finally is not standard. There's a good chance whatever framework you are using might expose finally, but if you want to use the standard Promise object, you'll have to stick to then and catch:

doSomeAsyncAction().then(resp => /* do something with the result */)
                   .catch(err => /* do something with the error... */)
                   .then(() => /* do any final cleanup */);

Up Next...

I think we've about beaten this Promise horse to death. All that remains now is to look at how we can combine promises with the new ES7 async/await capabilities that are Coming Soon to a browser near you!

About Matt Honeycutt...

Matt Honeycutt is a software architect specializing in ASP.NET web applications, particularly ASP.NET MVC. He has over a decade of experience in building (and testing!) web applications. He’s an avid practitioner of Test-Driven Development, creating both the SpecsFor and SpecsFor.Mvc frameworks.

He's also an author for Pluralsight, where he publishes courses on everything from web applications to testing!

blog comments powered by Disqus