DEV Community

loading...

Making Promises in JavaScript

szam profile image Samuel Grasse-Haroldsen Updated on ・3 min read

Synchronous Code

Most JavaScript code works synchronously. This means the code executes line by line:

function sum(a, b) {
  return a + b;
}

let answer = sum(2, 2);

console.log(answer);
Enter fullscreen mode Exit fullscreen mode

In the example above answer is calculated based on the result of sum. The console.log does not run until the sum function returns a value to the answer variable. This is what synchronous code does.

Asynchronous Code

Sometimes we have code that is very resource intensive. It may appear our app is unresponsive when in reality it is working hard. Most modern technology takes advantage of multiple processor cores. This means that we can run different code on different processors at the same time. We could fetch a list of articles while we are rendering an image. This gives our applications a huge boost in performance! JavaScript gives us a few very simple ways to make asynchronous code.

Callbacks

In JavaScript we can't talk about async code without talking about callbacks. Callbacks are functions passed to other functions that are then called in the function they were passed to. Not all callbacks are asynchronous, but by looking at how they work, we can see how we can chain async functions (more about this later). A common async callback is the second parameter in an addEventListener.

btn.addEventListener('click', () => {
  alert('Clicked!');
});

Enter fullscreen mode Exit fullscreen mode

Here we are passing an anonymous function to addEventListener that will be executed when our button is clicked (the anonymous function is the callback). This function isn't executed right away (we have to click on the button for it to execute). It is executed asynchronously. Callbacks can take arguments just like any other function. That means we can fetch or calulate data, pass that data to the callback function and do something with it.

function logResult(result) {
  console.log(result);
}

function sumAndSomething(a, b, callback) {
  let result = a + b;
  callback(result);
}

sumAndSomething(2, 2, logResult); // this will console.log(4)
Enter fullscreen mode Exit fullscreen mode

Although this example is contrived, we will see more natural examples of callbacks in the coming section. Callbacks are what make promises so powerful.

Promises

A Promise is an object representing the eventual completion or failure of an asynchronous operation. -MDN

Just like the developers at Mozilla have stated, a promise is simply an object with a message of success or failure. We can use promises to chain code. This means we can execute an async function, do something with the result, do something with that result and so on. It is this chaining that makes promises so powerful. If we were to fetch data from an API, we would probably want to display it. Fetch is naturally async and returns a promise. Here is an example of chaining using the .then method.

fetch('https://www.dnd5eapi.co/api/spells/') // fetch dnd spells
    .then(response => response.json())        // focus in on the json part of the response
    .then(spells => {                        
      console.log(spells);                   // log the spells
      renderSpells(spells);                  // render the spells to the DOM
    });
Enter fullscreen mode Exit fullscreen mode

Here we can see that fetch returns a promise and we are calling .then on that promise object which returns another promise. This is the chaining I was talking about.

Creating a Promise Object

As well as having a .then method, Promises also have a .resolve and a .reject method. We can control what our code should do if we run into problems. As you can probably guess, our promise resolves if we successfully complete our intended plan. If our code fails the promise rejects. We realize the importance of this when chaining our .thens.

function sum(a, b) {
  let result = a + b;
  return new Promise((resolve, reject) => {
    if(!isNaN(result)) {
      resolve(result);
    } else {
      reject(new Error('Sum could not be calculated.'));
    }
  });
}
Enter fullscreen mode Exit fullscreen mode

Here we are returning a resolved promise that contains our result if we have a valid result(non-NaN). If we do not have a valid result, we return a rejected promise with our custom error. As I said before we need to include the rejects so we can catch them.

sum(NaN, 2).then(r => console.log(r)).catch(error => console.log(error));
// alternatively we can pass a second callback to .then (this does the same thing)
sum(NaN, 2).then(r => console.log(r), error => console.log(error)); 
Enter fullscreen mode Exit fullscreen mode

I won't get into why it is important to catch errors, but it is. Check out this article JavaScript's try-catch hid my bugs!?. Next week I'll write about async/await, a contemporary way to make code asynchronous. I promise!

Discussion (0)

pic
Editor guide