Asynchronous JavaScript: The Complete Guide - Part 4 - Using Promises

Ronaldo B.August 1, 202210 min read

In this article

In this article we will talk about promises. If you wish, you can check the other posts in this series at these links:

  1. Asynchronous JavaScript: The Complete Guide - Part 1 - What is an asynchronous code?
  2. Asynchronous JavaScript: The Complete Guide - Part 2 - Callbacks
  3. Asynchronous JavaScript: The Complete Guide - Part 3 - Training Callbacks

In the last articles, we learned about callbacks, and we saw how these functions help us in asynchronous requests. However, in June 2015, we welcomed the new version of EcmaScript, version ES2015, or ES6. One of the innovations that this version brought was the concept of promises. They came to facilitate the passing of callback functions and also help us work with asynchronous codes.

What are promises?

“Promise” means promise. So, promises represent a promise: they will try to execute some function for us and let us know if they managed to fulfill their mission or not. In other words, a promise is an object that represents the failure or success of a code.

To better understand, think of this illustration: when we go to a job interview, we have the obvious goal of getting that job. After the interview, usually the representative of the company where we went tells us the following phrase: “If your profile pleases us, we will contact you.” This is a promise, and we hope it becomes a reality. However, the call from this company confirming our hiring is something that is beyond our control. It may work out, but it may not; we cannot control that. What we can do is prepare for the response, whatever it may be. This is the concept of a promise. We inform a function for it. It may work or not, we do not know what will happen. What we can do is prepare for success or failure in executing the function. This is something amazing because it gives us greater control over our code, including error handling, something that callback functions do not offer us by default.

Basic syntax of a promise

To use a promise, it is necessary to create a new instance of the Promise object. This object will wait for a function as an argument, which in turn will wait for two parameters: 1) resolve, 2) reject. Both are functions:

  1. resolve(). It is the function that will confirm the success of the operation if everything goes well with the promise.

  2. reject(). It is the function that will confirm the “failure” of the operation if something goes wrong with the promise. This can mean an error or simply an exception, something that is outside what we expect for our function.

Just one detail: these names help us understand the purpose of each function. However, as they are represented by variables, we can name them whatever we want.

See below the creation of a promise:

function addNumbers(numberA, numberB) {
	return new Promise((resolve, reject) => {
		let result = numberA + numberB;
		if (result > 0) {
			resolve(result);
		} else {
			reject('The result is invalid');
		}
	});
}
addNumbers(2, 5);

Explaining: we created a function that will sum two numbers. This function returns a promise, with the parameters we talked about above: resolve and reject. Notice that we used arrow functions in this example. Inside the promise, we do a check: if the result of the calculation is greater than zero, that is, if there is a result, we will execute resolve(), informing that everything went well. Notice that we will even inform what the actual result was. However, if there is no result, we will reject the promise with the reject() method, making it clear that there was some error or exception via a string. This teaches us another thing: we can inform any type of data as a parameter for these functions: a string, a variable, a number, or even another promise.

In addition to the resolve() and reject() methods, promises also have two very interesting methods: 1) Promise.all() and 2) Promise.race()

  1. Promise.all(). Expects a series of promises as a parameter and will return a success (resolve) when all of them execute correctly, or it will return a failure (reject) if any promise is not completely executed. It is a method used when we need to execute several promises at the same time.

  2. Promise.race(). Expects a series of promises as a parameter and will return a success (resolve) as soon as the first promise executes correctly, or it will return a failure (reject) when a promise is not completely executed.

We see that it is quite simple to create a promise. But how will we manipulate what it returns?

“Callback” in Promises

Promises offer us two methods we can use to manipulate the possible results: 1) then and 2) catch

  1. then(). This method receives what our resolve() informs. It is used to manipulate the success response of the promise.

  2. catch(). This method receives what our reject() informs. It is used to manipulate the error or exception response of the promise.

These methods will be defined where the promise was called. See an example below:

addNumbers(2, 5)
	.then(result => {
		console.log(`The result is ${result}`);
	})
	.catch(error => {
		console.error(`An error occurred: ${error}`);
	});

Explaining: The methods were executed below the call to addNumbers(). Something very cool we can see is that it is possible to break lines when calling these methods, which makes the code easier to read. Also, note that the information we returned when creating the promise was identified in the code above as two variables: result and error. Again, since they are variables, we can feel free to choose their names.

And now we can understand a very important detail: since these two methods are executed after the execution of a main function, which is the function contained in our promise, we can say that then() and catch() fit the concept of callback functions. Amazing, don’t you agree? We can see then that promises are an alternative way to create callback functions. It is even possible to say that it is better to use promises. Why do we say that?

Advantages of Promises

One of the main advantages of using promises instead of callbacks is what we call chaining. This represents calling several functions in sequence, one after the other. When we do this with callbacks, our code can become a bit “messy”, difficult to understand and maintain. See this example: let's create three functions that will display information in the console, using the “classic” syntax of callback functions. The functions are as follows:

function writeHello(callback) {
	console.log('Hello');
	callback();
}

function writeGoodBye(callback2) {
	console.log('GoodBye');
	callback2();
}

function writeSeeYouLater(callback3) {
	console.log('See you later');
	callback3();
}

Each of them will call their respective callback. Let's call them:

writeHello(function(){
	writeGoodBye(function() {
		writeSeeYouLater(function() {
			console.log('The code has finished')
		})
	})
})

This code represents a chaining. We call one function after the other. But there’s one thing: didn’t the code get a bit strange? It looks like a pyramid of codes, doesn’t it? If this structure grows a bit more, it will become difficult to maintain these functions. Now see how the creation of these same functions with promises would look:

function writeHello() {
	return new Promise((s, f) => {
		console.log('Hello');
		s();
	});
}

function writeGoodBye() {
	return new Promise((s2, f) => {
		console.log('GoodBye');
		s2();
	});
}

function writeSeeYouLater() {
	return new Promise((s3, f) => {
		console.log('See you later');
		s3();
	});
}

Notice that now we call resolve with the letter “s”, representing the success of the execution, but it could be any other identifier.

See below how the call of these functions would look now:

writeHello()
	.then(writeGoodBye())
	.then(writeSeeYouLater())
	.then(() => {
		console.log('The code has finished');
	})

We have just converted our code into a promise chain, which will continue to work smoothly. However, our code is now much more intuitive, simpler, and easier to understand. It may be that we even need to write a bit more, but the code is much more organized.

We conclude that promises are an evolution of callbacks and they greatly facilitate the calling of asynchronous codes. What do you think about putting the concept of promises into practice in the Hcode Games project we are developing together? Let’s go then!

We need to remember where we were using callbacks in the code: it was in the requests.js file. It will be altered first. The getGameByName() and getRelatedGamesByName() functions were expecting a variable called callback; this will no longer be necessary.

Now both functions will return promises, with the resolve and reject variables as parameters. Additionally, we can adapt the way to perform the request. We were using the XMLHttpRequest object. However, we can use a new tool that JavaScript provides us: the Fetch API. It is much simpler to use for making requests to a server, and it is being referred to as “the new AJAX.”

To use this resource, just call the fetch() function. It expects the request URL as a parameter; and the best part: can you imagine what it returns? A promise :). See below how our getGameByName() function will look:

function getGameByName(name) {
	return new Promise((resolve, reject) => {
		fetch(`https://api.rawg.io/api/games?search=${name}`)
			.then(res => res.json())
			.then(data => {
				resolve(data);
			})
			.catch(err => {
				reject(err);
			})
		});
}

Explaining: inside the promise we created, we call the fetch() function. As this function returns a promise, we use the .then() and .catch() methods.

Remembering that we talked about Promises and Fetch API also in our Complete JavaScript Course.

Back to the code. Notice that we created two .then() methods. Why? The first will convert the server’s response to JSON; this code corresponds to executing the JSON.parse() function, as was in the previous code, but now it is much simpler. Thus, note that we can call more than one .then() method in the same promise, if we wish.

Another important detail is that now the error message or the results in JSON will be returned via reject() or resolve(). We will follow the same pattern for the getRelatedGamesByName() function:

function getRelatedGamesByName(name) {
	return new Promise((resolve, reject) => {
		fetch(`https://api.rawg.io/api/games/${name}/suggested`)
			.then(res => res.json())
			.then(data => {
				resolve(data);
			})
			.catch(err => {
				reject(err);
			})
		});
}

Perfect! Now we can go to our HTML file.

We called these functions only in the initGames() function and it will be the one we will alter. Now we will only inform the name of the game to the functions and instead of adding the callback code in an anonymous function, we will use the .then() method. The final code will look like this:

function initGames(gamename) {

   setGameLoad(gamesListPrincipal);

   gamesListRelationed.innerHTML = '';

   getGameByName(gamename).then(games => {

       gamesListPrincipal.innerHTML = '';

       games.results.forEach(game => {

           let divGame = setGameHTML(game);

           divGame.addEventListener('click', e => {

               setGameLoad(gamesListRelationed);

               let gameTag = e.currentTarget;

               let gamename = gameTag.dataset.gamename;

               getRelatedGamesByName(gamename).then(gamesRelationed => {

                   gamesListRelationed.innerHTML = '';

                   gamesRelationed.results.forEach(game => {

                       let divGameRelationed = setGameHTML(game);

                       gamesListRelationed.append(divGameRelationed);

                   });

               });

           });

           gamesListPrincipal.append(divGame);

       });

   });

}

Changes completed! Now, as the title of the first Karate Kid said: “Now is the time for truth.” Our project needs to keep working. Let’s test it:

Hcode Games list

Hcode Games list

Great, it continues to work the same as before, but now our project is modeled on the concept of Promises.

In this article, we learned what Promises are in JavaScript, and I promise they will help you a lot in development (sorry for the pun, I couldn't resist). You can find all the codes developed in this article at this link: https://github.com/hcodebr/javascript-assincrono-blog

If you liked the content of this article, we ask you to share it so that other people can also access this material, thank you very much!

Promises are really amazing, very easy to implement. But JavaScript still has another resource, introduced in its latest version, that also greatly helps in asynchronous codes, further facilitating the use of promises. We are talking about asynchronous functions, or async/await. We will talk about them in more detail in the next article: Asynchronous JavaScript: The Complete Guide - Part 5 - Understanding Async/Await, see you then :)

Keep learning with Hcode

Sign up for the course platform and follow the next blog guides.