DEV Community

Cover image for Promises: The Ability to Write Asynchronous Code Using Javascript
Evan Loria
Evan Loria

Posted on

Promises: The Ability to Write Asynchronous Code Using Javascript

Introduction

By nature, Javascript is a synchronous, single-threaded programming language. This means that operations are run one at a time, in the order they were called. Web applications need the ability to perform multiple operations at the same time. Using synchronous code, a program would have to wait for an operation to complete before the program could continue running. For this reason, Javascript offers a way for developers to create asynchronous operations that perform in the background, allowing code to continue running before an operation is completed.

Asynchronous Callbacks

A callback is a function that is passed into another function to be called at the appropriate time. Before the introduction of promises in Javascript ES6, callbacks were used to handle asynchronous operations. Because Javascript uses an event loop to track the order in which functions should be executed, functions are able to execute in a different order in which they were called.

const addOne = function(number) {
  number += 1;
  return number;
};
const addNextNumber = function (number, addOne) {
  return number + addOne(number);
}
addNextNumber(5);
Enter fullscreen mode Exit fullscreen mode

The addNextNumber function relies on the return of the addOne function. The addOne function finishes executing, and then the addNextNumber function is able to complete its operation.

However, if we need to pass multiple callbacks into one another our code can become difficult to understand. When callbacks become overly nested it is called callback hell, or the pyramid of doom.

Example From MDN:

function doStep1(init, callback) {
  const result = init + 1;
  callback(result);
}
function doStep2(init, callback) {
  const result = init + 2;
  callback(result);
}
function doStep3(init, callback) {
  const result = init + 3;
  callback(result);
}
function doOperation() {
  doStep1(0, (result1) => {
    doStep2(result1, (result2) => {
      doStep3(result2, (result3) => {
        console.log(`result: ${result3}`);
      });
    });
  });
}
doOperation();
Enter fullscreen mode Exit fullscreen mode

You can see how nesting callbacks can become difficult to read when callback functions take in another callback. Especially when dealing with more complicated callbacks that fetch data from a server.

Promises

A promise is a unique object that is returned by an asynchronous function that represents the eventual success or failure of an asynchronous operation. Promises are the foundation of asynchronous Javascript. Promise objects contain two properties: state and result:

State Property Value Options

  • Pending: The function that created the promise has not finished executing
  • Fulfilled: The asynchronous function succeeded
  • Rejected: The asynchronous function failed

Result Property Value Options

  • The value of the successful result
  • The value of the failed result
  • Undefined

It is worth noting that the above properties are not code-accessible. We can only view them using a debugger tool.

The Fetch API is what javascript uses to make requests to a server. It replaced XMLHttpRequest, which uses callbacks. A request is made by calling fetch(), which can take in an object, or a string url of a resource to send a request to. The return value of fetch() is a promise object.

Promise Handlers

The promise object supplies methods to deal with the result of a promise. We will be talking about the .then() and .catch() handlers, but you can find other promise handlers here.

.then() Handler

.then() takes in two callback arguments: one for the case of a fulfilled promise, and one for the rejected promise. This handler is capable of returning a promise, returning a value, or throwing an error.
Let's set a variable equal to the promise returned by invoking fetch()

const ourPromise = fetch("https://example.com/post");
Enter fullscreen mode Exit fullscreen mode

We can attach then() to our promise, and once the promise is fulfilled or rejected it will perform the corresponding callback argument.

const fulfilledCB = function() {
console.log('Promise was successful');
};
const rejectedCB = function() {
console.log('Promise failed');
};
ourPromise.then(fulfilledCB, rejectedCB); 
Enter fullscreen mode Exit fullscreen mode

If our promise is returned successfully, our fulfilledCB will run. If the promise was rejected, then our rejectedCB will run. We can optionally only pass in a callback for the value we are interested in, setting the unwanted value's callback to null.

Chaining Promises

Once a response object is received using fetch(), we need to use another function to access the data of the response. We can do this using the json() method to transform our promise into a json object.

const ourPromise = fetch("https://example.com/post");
ourPromise.then((response) => {
return response.json();
}).then((user) => {
console.log(user.name)
});
Enter fullscreen mode Exit fullscreen mode

We use our first .then() to transform the promise into json, and the second to access the data within our response object.

.catch()Handler

You may have noticed that in the above code snippet we did not provide a callback to handle rejected promises. When using promise chaining, we attach the .catch handler to the end of our chain. This handler will take in a callback to deal with any promise that was rejected at any point in the chain. .catch() can also be used with only one .then() rather than passing in an error callback to .then().

const ourPromise = fetch("https://example.com/post");
ourPromise.then((response) => {
return response.json();
}).then((user) => {
console.log(user.name)
}).catch(() => {
console.log('One of your promises was rejected');
};
Enter fullscreen mode Exit fullscreen mode

.catch() offers an extremely simple way to handle errors when dealing with asynchronous code.

Conclusion

In order for our programs to run as efficiently as possible, it is imperative that our code have the ability to run asynchronously. Long-running synchronous code would cause a program to become unresponsive while it waited on the return of an operation. Promises offer an elegant solution to writing asynchronous code by improving code readability, simplifying error handling, and allowing developers to avoid callback hell.

Sources:

MDN Promises
MDN Introduction to Asynchronous Code
Free Code Camp Asynchronous Javascript
Green Roots Promises Explained Like I Am Five
Green Roots Handling Promises
Dev Community Callbacks, Promises, and Async/Wait

Top comments (0)