How promises work in JavaScript

Mastering the concept of promises is a cornerstone of efficient JavaScript. It makes the task of dealing with asynchronous requests much easier and reduces the potential complexity of your code.

But what exactly are promises? How, when and where would you use promises? And how does promise chaining work?

Well, you’re in the right place.

What exactly are JavaScript promises?

The way JavaScript works is that it executes one thing and moves right onto the next thing. But sometimes you need the code to wait.

JavaScript’s natural flow doesn’t wait for anything and if the value that’s given isn’t right there when it needs it, it will be evaluated based on its current state and not what it's supposed to be.

This means a variable could be temporary null or undefined— throwing your entire result out of whack.

JavaScript promises allows you to create code that can run in a separate and independent order than the usual flow of things.

Take a look at the diagram below.

The time it takes for getCat() to call the Cat Details API and get some results back can cause the flow in the first column of functions to break. This is because the JavaScript interpreter will continue with the code, result in an incorrect set of parameters and injections due to the delay.

However, promises let us explicitly tell the JavaScript engine to wait for a result rather than continue on as per usual. It turns your code into an asynchronous function — which means that the block of code operates in a separate manner from everything else.

How do you write JavaScript promises?

In general, the concept of promises is easy. It’s figuring out how to write a JavaScript promise that can be difficult. So rather than just giving you the code right off the bat, let’s break it down instead.

There are three parts to a promise. These parts are called states. When a promise is called, it can be any one of these three: pending, fulfilled or rejected.

When you envoke a promise, you are calling a function that tells your JavaScript interpreter to wait for a result. When a result is returned, the process of a promise is considered completed or settled.

In bare-bones code form, it looks something like this:

var someFunction = new Promise(function(resolve, reject){    
   //do something async here like call an external API...    
   if(/*the parameter test that everything went ok*/){       
      resolve();      
   }else{        
      reject();   
   }
  });

Promises are JavaScript objects that may produce a value in the future. You must call the resolve() or reject() method in order to end/settle a promise.

A callback is something you use to handle what happens after the promise is settled. The value/data obtained through the promise can then be processed accordingly.

A callback is written with a .then() method that lets you accept parameters from the promise result and looks something like this:

someFunction.then((resolvedObj) => {   
   //resolvedObj is whatever you passed through resolve()
});

When and where would you use a JavaScript promise?

Promises are often used when your app needs to interact with and use external interfaces. These interfaces often come in the form of APIs that connect to databases.

We would use promises in this scenario because there is a time latency involved and using a promise will force JavaScript to wait for a returned result.

When a promise gets settled, it cannot be resettled. This means that a settled promise is immutable. You can, technically, call resolve() or reject() again but it won’t have an effect on your result. This is important, especially for functional flows, where immutability is a major feature.

It’s good to note that a promise is a single event. it is also eager in nature, meaning that it will start doing its task as soon as you call it.

Once a promise starts its process, it cannot be canceled and can only settle through resolve() or reject(). You can use resolve() or reject() as-is, or pass it a string, object or array to be consumed by a callback.

This is different from an observable — which follows a similar concept to promises but deals with a stream of events rather than a single event. observables are lazy in contrast and may not always begin when it is invoked. Another major difference is that observables can be canceled and the HTTP request results will stop on demand.

How does promise chaining work?

It’s easy to write a promise. But what if you have more than one promise? How do you coordinate and use multiple promises in succession with one another?

JavaScript promises are thenable objects. This means you can use the then() method multiple times to create a chain that makes sense for your app. Using then() allows you to run your code in a serial sequence.

So your code can end up looking something like this:

fetchNetflix(show)   
.then(processShow)   
.then(saveSettings)   
.catch(handleErrors)

So your code can end up looking something like this:

someFunction()   
.then(handleSuccess,handleNetworkError)   
.catch(catchAllError)

Bonus: use Tech Debt Tracker to keep your promises clean

On the surface, promises are simple to write by design.

However, it’s the stuff that you put inside a promise that can balloon out and create technical debt in the long run. Technical debt is something that is unavoidable and often arises over time with a compounding effect.

When it comes to technical debt, there’s more to it than just the number of lines you create. Clean code is determined by multiple factors and can be measured through length, argument count, complexity, understandability, nesting depth and comment density.

At Stepsize, we’ve come up with a tool — Tech Debt Tracker — to help keep your code succinct and prevent the long term accumulation of potential frustration and growth of bugs.

Keeping your promises clean can help increase its modularity and make it easier to understand, especially in a team-based setting.

Working in a team can come with its own set of challenges and keeping code cohesive is one of them. As a Visual Studio Code extension, you can just install it and use the tool to monitor and maintain the different metrics that you can use as guidance.

So if you haven’t done so already, be sure to give the extension a whirl and see how it can benefit the cleanliness of your code in the long run.