...
JavaScript Promises 5 Essential Concepts

JavaScript Promises: 5 Essential Concepts

Creating and working with JavaScript promises can be somewhat of a trial by fire. The moment you have had to wrestle with network requests, timers, etc., you have probably experienced that pyramidal horror where your code has indented so far to the right that it appears to fall off the left side of the screen.

The introduction of JavaScript promises was meant to help prevent such monstrosities. While they may appear somewhat abstract at first, once you grasp the basics of how they work under the hood, they quickly become one of the most valuable tools in your arsenal.

In simple terms, a promise is a substitute for a value whose actual existence is uncertain at the time the promise is made. To illustrate this concept further, consider the small plastic token commonly given to customers at a busy coffee shop as soon as they place their drink orders. When you receive this token (which serves as a representation of your coffee), rather than stand in front of others waiting for your turn to pick up your coffee, you can go back to sitting down and scrolling on your mobile device. Once the token buzzes and turns green, you know your value (caffeine) is ready.

With this idea in mind, let’s take a closer look at five fundamental concepts that should give you confidence when dealing with JavaScript promises and eliminate some of the “guess work” involved in doing so.

Understanding the Three Main Methods of JavaScript Promises: Then, Catch, and Finally

At its base level, a promise exists in one of three states; pending (you’re still waiting for your coffee), fulfilled (your coffee has arrived), or rejected (the barista spilled coffee all over your table and now there is oat milk everywhere). Once a promise reaches either fulfilled or rejected state, it is no longer pending.

There are three main ways to communicate with a promise; then, catch, and finally. The then method allows you to capture the value passed along within a promise when that promise is fulfilled. The catch method is used as a safety net. It enables you to define actions to perform when a promise is rejected. Since an unhandled rejection will break your app silently, you need to add a catch block whenever you create a promise.

Finally is relatively new. Prior to ES2018, if you wanted to hide away your loading spinner or close database connections regardless of whether the promise succeeded or failed, you would typically duplicate code found in both a then and a catch block. The finally method allows you to define a block of cleanup code that runs regardless of the outcome (success or error) of your promise.

Getting comfortable with these three methods is key to creating robust JavaScript promises.

Working with Multiple Tasks via Static Methods Attached Directly to the Promise Object

While having experience working with a single promise is great, many apps require managing multiple asynchronous processes simultaneously. This is where static methods available directly on the Promise object come into play. You’ve undoubtedly seen Promise.all before. It’s the ‘all or nothing’ approach. It won’t resolve until all promises in an array are resolved. However, if even one promise in that array rejects, Promise.all instantly rejects ignoring all remaining promises in that array.

Use Promise.all when you absolutely need all values before rendering anything on the screen. No missing users. No partially loaded dashboards.

What about if you’re sending a bunch of files and one fails? Why lose track of the status of those that succeeded? That’s exactly why you’d use Promise.allSettled. This method waits for all promises in the provided array to settle (regardless of being successful or failing), and returns an array of result objects detailing the status and value/reason for each. It never rejects, it only resolves with an array of result objects showing you exactly what went wrong.

On the opposite end of the spectrum lies Promise.race. Its purpose is clear based on its name. It waits for the first promise in the provided array to settle and returns that value. If that value represents a success or failure is irrelevant; it’ll be whatever was produced by the first promise that finished settling. This is useful when you want to implement a timeout. You could easily race your api call against a setTimeout(5000) that rejects after five seconds. If the setTimeout call settles first, the operation will fail fast instead of hang indefinitely.

Lastly, there is Promise.any that behaves similarly to Promise.race except instead of returning the value associated with the first resolved promise, it waits for the first successful resolution (and ignores rejections unless all promises reject).

Mastering Chainable Promises

One of the most beautiful aspects of promises is that they allow for chainability. A chainable promise is defined as follows:

A chainable promise is a promise returned by a previously returned promise’s `then` method.

Because then doesn’t just consume a value but also returns another promise, you can link together multiple asynchronous operations into a flat, easy to read sequence.

Why Chainability Is Important

Let’s imagine you’re building an app where users need to login before accessing certain content. After logging in, you need to fetch their profile information and their permissions.

Prior to promises and async/await, performing this kind of logic required a lot of deeply nested callbacks.

Promises provide a better way. You can chain these operations together neatly.

Fetch Token → Get Profile → Fetch Permissions

Here’s how:

1.) Call `fetchToken`, get a promise.
2.) Inside `then`, return the next promise (`getProfile()`).
3.) The value from `getProfile()` automatically flows into `then` in `fetchPermissions()`.
4.) Keep going as needed.
5.) Have only one `catch` block at the bottom of the chain to deal with any error occurring anywhere in the entire sequence.

Chains enforce a linear flow of data that is much easier to debug. Instead of tracking variables across 5+ levels of deeply nested anonymous functions, you’re just moving data down a clean, structured pipe.

For more information on chained promises check out this article from javascript.info which includes nice visual examples.

Using Syntactic Sugar with Async/Await

Async/Await can be thought of as a layer of abstraction on top of promises. It’s designed to make asynchronous programming feel as natural as possible.

When writing asynchronous code with async/await, you’ll find yourself writing top down code that reads naturally, without the pyramid of doom callback hell.

You declare a function as “async” and inside that function you use “await”. When you use “await”, execution of that specific function pauses until the awaited promise resolves and then it unpacks the resulting value for you.

One big advantage here is that error handling becomes much cleaner. You can replace long chains of catch statements with regular try/catch blocks which read intuitively because they mirror how you’ve already been handling synchronous errors.

However there’s tradeoff. Async/Await makes writing sequential tasks incredibly easy. But if you have 5 separate API calls happening independently Promise.all remains the far superior choice.

If you attempt to use await in a loop for independent tasks, each call will wait for the prior call to complete and thus cause unnecessary slowdown due to lack of concurrency.

Know when to use syntactic sugar and when to reach for raw power.

Common Mistakes Made Working with Promises

Even seasoned developers occasionally slip up when working with promises. Perhaps the most common mistake made is referred to as “the explicit promise construction anti pattern.” This type of mistake occurs when someone wraps a function that already returns a promise in new Promise().

If myPromiseFn already gives me a promise, I don’t need to wrap it in yet another promise. Not only does this introduce verbosity, it increases my chances of losing potential errors somewhere along the way.

Another potentially silent error is forgetting to return a promise from inside a then block. Let’s say you have two nested promises but forget to return the second promise. Your outer chain will continue executing before the inner process completes, potentially causing race conditions that are hard to debug.

Lastly, while many people assume Promise.all will wait for all tasks to complete before resolving, it actually short circuits and rejects immediately upon encountering its first failure.

If you want an array containing details about all successes AND failures, you should use Promise.allSettled instead.

New to HTML? Start Here: HTML Tutorial for Beginners: Your Complete Introduction to HTML Basics
New to CSS? Start Here: CSS Introduction: Master 5 Core Concepts Easily
New to JavaScript? JavaScript Introduction: 5 Proven Steps to Learn JS

[INSERT_ELEMENTOR id=”122″]

Leave a Comment

Your email address will not be published. Required fields are marked *

Seraphinite AcceleratorOptimized by Seraphinite Accelerator
Turns on site high speed to be attractive for people and search engines.