JavaScript Promises Made Easy

Published: 16th May, 2024

Author: Sohan Shashikumar

Read Time: 5 minutes

Intro

A promise is a special kind of object that determines the eventual completion/failure of an asynchronous operation.

Creating a Promise

A promise is created by using the Promise() constructor. It takes a function as an argument, called the executor. The executor in-turn has access to two methods, resolve and reject which are called to mark the success or failure of an async operation respectively.

Lots of talk, lets jump into the code!

const myPromise = new Promise((resolve, reject) => {
	console.log('Hello World');
});
  • What's going on here? We are calling the Promise constructor and passing it a callback function. This is the executor function.
  • The executor runs immediately when the promise is constructed, meaning the above code logs Hello World to the console when executed.
  • That doesn't quite define how async JavaScript works, so lets add a timeout and pretend we are making a network request that takes 2 seconds to complete.

Simulating a network request

const myPromise = new Promise((resolve, reject) => {
	setTimeout(() => {
		// this function executes after 2 seconds
		const data = {
			name: 'sohan',
			age: 21
		};
		resolve(data);
	}, 2000);
});

In the executor, we are using a setTimeout which is built into JavaScript, that executes a function after a specific delay. After 2 seconds, we are creating a data object with the name and age property. Notice that we are calling the resolve method. Try running this code.

Nothing happens!

This is because we are not handling what happens after the promise has been fulfilled/successful. A promise object provides three special methods to handle the outcome of a promise.

They are

  • then(): Used to handle successful operations.
  • catch(): Used to handle failed operations.
  • finally(): Used to perform any cleanup operations regardless of the outcome.

lets make some modifications to the above code.

const myPromise = new Promise((resolve, reject) => {
	setTimeout(() => {
		// this function executes after 2 seconds
		const data = {
			name: 'sohan',
			age: 21
		};
		resolve(data);
	}, 2000);
});

myPromise.then((response) => {
	console.log(response);
});

We are attaching a .then() to the created promise that takes a callback function which gets called after the promise resolves i.e when resolve() is called. We also have access to the response which is basically the data object that is passed to resolve in the executor, received after 2 seconds.

It is important to note that the value with which resolve() is called with is available in the callback passed to .then()

Try running this code, you will see the data object we created, logged to the console after 2 seconds

How is it asynchronous you may ask

Let's make a slight modification to the existing code.

console.log('start');
const myPromise = new Promise((resolve, reject) => {
	setTimeout(() => {
		// this function executes after 2 seconds
		const data = {
			name: 'sohan',
			age: 21
		};
		resolve(data);
	}, 2000);
});
	
myPromise.then((data) => {
	console.log(data);
});
	
console.log('end');

Take a pause to guess the outcome of the code!

Output

start 
end
{ name: "sohan", age: 21 }

Congrats to those who got it right 🍻 Don't worry if you didn't, let's break it down line by line!

Step 1

On line 1, we are simply logging start to the console. Then we are creating a promise object and passing it an executor. The executor runs, schedules a setTimeout that needs to run after 2 seconds.

Step 2

We attach a .then() callback and pass it a function that needs to execute after the promise is fulfilled i.e after 2 seconds.

Step 3

The control goes to the next line and logs end. Notice how it does not wait for those 2 seconds to elapse before logging end? That is how its being asynchronous. It does not block the main thread.

Real World Example

console.log('start');
	
fetch('https://jsonplaceholder.typicode.com/todos/1')
	.then((response) => response.json())
	.then((json) => console.log(json));

console.log('end');

Here, we are actually performing a network request to load some data. We are using the JavaScript native fetch() API. The call to fetch() returns a Promise which means we can use .then() .catch() and .finally()

Think of it like a step by step process of receiving the outcome. Take a pause to guess the outcome, yet again

Output

start 
end
{
	"userId": 1,
	"title": "delectus aut autem"
}

hmmmmm Pretty strange? Why? We aren't setting a timeout but yet our result is being logged at the end. That's because, in the real world, network requests take time and we cannot determine how long it would take for the data to be fetched.

If there is a serious issue at the server's end, our code will keep waiting forever. We do not want to write code like this in a production environment. This is exactly what promises do.

What about errors?

Let's modify the above code

console.log('start');

fetch('https://supersecretwebsitethatdoesnotexist.com/data')
	.then((response) => response.json())
	.then((json) => console.log(json))
	.catch((err) => console.log(`Whoops! Something went wrong`, err));
	
console.log('end');

It's always good to handle errors as it will help your users know if something went wrong instead of infinite loading screens. You as the developer will also understand what went wrong through your application support tickets.

In the code above, we are making a request to a url that does not exist. Try running it. We'll run into an error. Notice how we are also handing it? This is done using the .catch() block. Here you can write code to get notified about it or send it to an error logging service like datadog or sentry.

Conclusion

  • A promise in JavaScript is used to complete an asynchronous task without blocking the event loop.
  • Non blocking code always ensures the application runs smoothly as expected.
  • A promise is created by creating a new instance of Promise()
  • A promise has access to special methods which are .then(), .catch() and .finally() which are used to consume, handle errors and perform cleanup operations respectively.
  • .then() also returns a promise, meaning they can be chained.

Feedback 🛠️

Your feedback is highly appreciated and will be used to improve the quality of existing & future posts!

What do you rate this post?

Anything else you wish to add?