DEV Community

loading...
Cover image for Promises in JavaScript

Promises in JavaScript

peterklingelhofer profile image Peter Klingelhofer ・3 min read

Introduction

Callbacks are functions that run after something happens or something completes. If you have to make an asynchronous request, it can either be successful or result in failure. These two possibilities in Promises are denoted by resolves or rejects parameters.

Promises help us avoid what's commonly referred to as "callback hell", wherein cascading callbacks create a pyramid shape and impossible to decipher code that's hard to scale. Promises allow us to easily chain callbacks in an intuitive way, by chaining then and using catch for errors. Promises helps to break up callbacks into methods with semantic naming.

A Simple Promise

const promise = new Promise(function (resolve, reject) {
  const a = 'apples';
  const b = 'apples';
  if (a === b) {
    resolve();
  } else {
    reject();
  }
});

promise
  .then(function () {
    console.log('success');
  })
  .catch(function () {
    console.log('error');
  });

// output => 'success'

Concepts to Know

  • Deferred objects are works that are not yet completed
  • Promise properties are placeholders. Initially, this value is unknown.
  • Each deferred has a promise which acts as a placeholder for the future result.

Support

All modern browsers support Promises, except for Internet Explorer 11 and previous versions.
Alt Text

API Request Example with Promises

Let's pretend that we have a website with multiple accounts created for people's pets. Below, we create a promise to request a made up api server a specified number of pets' user accounts. If the statusCode associated with the request is 200, that indicates that the request was successful, and that we should call resolves to utilize JSON.parse to parse the results. If we do not receive a 200 statusCode back, rejects is called, showing the user the failing statusCode.

// creating the request

const getPets = (number) => {
  new Promise((resolves, rejects) => {
    const request = new XMLHttpRequest();
    request.open("GET", `https://api.examplepet.com/?results=${number}`);
    request.onload = () => {
      if (request.status === 200) {
        resolves(JSON.parse(request.response).results);
      } else {
        rejects(Error(request.statusText));
      }
      request.onerror = (error) => rejects(error);
      request.send();
    };
  });
};

// invoking the request
getPets(20)
  .then((pets) => console.log(pets))
  .catch((error) => console.error(error));

Promise Method: done()

Normally, when chaining method calls with Promises, you can risk discarding errors without seeing them:

function promisesExampleFunction() {
  asyncFunc()
  .then(function1)
  .catch(response1)
  .then(function2);
}

If the last .then(function2); line elicits a rejection, it is never handled. We can account for this using done() like so:

function promisesExampleFunction() {
  asyncFunc()
  .then(function1)
  .catch(response1)
  .done(function2);
}

In the example above, done() can actually replace the last .then(function2);, or it can be interchangeably added after the last .then(function2) with done() receiving no arguments.

Promise Method finally()

finally() is great for performing actions regardless of whether an error occurs. It is most frequently used as a clean up method after the last then() or done() call. This can be done during page loads to show and hide the spinner, regardless of whether the above functions were successful or not - you'll want to stop the spinner if processing is no longer occurring:

spinnerShow();
fetchData()
  .then((data) => renderPage(data))
  .catch(error)
  .finally(spinnerHide);

Promisify

Promisify is an easy way to convert callback functions to a function that returns Promises. Below, we convert a callback-based function called func to return native Promises.

const promisify = require("es6-promisify");
const fs = require("fs");

const promisifiedFunc = promisify(fs.func);

promisifiedFunc("oranges.txt").then(function (fruit) {
  console.log("Fruit received:", fruit);
}).catch(function (error) {
  console.error(error);
});

Conclusion

Promises after they're called upon eventually either resolve or reject, returning a response based on what the Promise object specifies. By chaining then and using catch for errors, we can perform asynchronous functions in an intuitive way and avoid "callback hell". Promises can handle multiple async operations seamlessly, while at the same time providing superior error handling than would be possible with simple callback chaining.

Discussion (0)

pic
Editor guide