DEV Community

Cover image for JS: Promise or Callback?
DariansDEV
DariansDEV

Posted on

JS: Promise or Callback?

Understanding Promises versus Callbacks in JavaScript

Key Questions and Answers for Certification Testing

  1. What is a callback function, and how does it differ from a regular function?

    • A callback function is passed as an argument to another function and is invoked to handle asynchronous operations. Unlike regular functions, callbacks are designed to be executed after a certain task is completed.
  2. How do Promises improve code readability and manage asynchronous operations compared to callbacks?

    • Promises allow for chaining and better error handling, leading to more readable and maintainable code. They help avoid deeply nested structures known as "callback hell."
  3. What are the main states of a Promise, and how do they transition between these states?

    • The main states are: Pending (initial state), Fulfilled (operation completed successfully), and Rejected (operation failed). A Promise transitions from Pending to either Fulfilled or Rejected.
  4. How can you handle errors using Promises, and how does this compare to error handling with callbacks?

    • Promises provide a catch method to handle errors in a streamlined manner, whereas error handling with callbacks often requires passing error objects and multiple checks in nested callbacks.
  5. What is the difference between Promise.all and Promise.race, and when would you use each?

    • Promise.all resolves when all input Promises resolve, making it useful for waiting for multiple asynchronous operations to complete. Promise.race resolves as soon as one of the input Promises resolves, useful for scenarios where the first completed operation's result is needed.
  6. How does the async/await syntax simplify working with Promises, and what are the rules for using await?

    • The async/await syntax allows writing asynchronous code in a synchronous manner, improving readability. await can only be used inside async functions and pauses the execution until the Promise resolves.

Introduction

In the evolving landscape of JavaScript, managing asynchronous operations efficiently is key to building performant web applications. While callbacks were the original approach, Promises have introduced a more structured and readable way to handle asynchronous tasks. This blog delves into the intricacies of using Promises versus callbacks, assuming you already have a foundational understanding of these concepts.

Benefits of Promises Over Callbacks

Improved Readability and Maintainability

Callbacks, though functional, often lead to deeply nested structures known as "callback hell," making the code difficult to read and maintain.

Example of Callback Hell:
fetchData(function(response1) {
  fetchMoreData(response1, function(response2) {
    fetchEvenMoreData(response2, function(response3) {
      console.log(response3);
    });
  });
});
Enter fullscreen mode Exit fullscreen mode
Improved with Promises:
fetchData()
  .then(response1 => fetchMoreData(response1))
  .then(response2 => fetchEvenMoreData(response2))
  .then(response3 => console.log(response3))
  .catch(error => console.error(error));
Enter fullscreen mode Exit fullscreen mode

Error Handling

With callbacks, error handling can become cumbersome as you need to pass error objects and handle them at each level.

Error Handling with Callbacks:
function fetchData(callback) {
  setTimeout(() => {
    if (/* error condition */) {
      callback(new Error('An error occurred'), null);
    } else {
      callback(null, 'data');
    }
  }, 1000);
}

fetchData((error, data) => {
  if (error) {
    console.error(error);
  } else {
    console.log(data);
  }
});
Enter fullscreen mode Exit fullscreen mode
Error Handling with Promises:
function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (/* error condition */) {
        reject(new Error('An error occurred'));
      } else {
        resolve('data');
      }
    }, 1000);
  });
}

fetchData()
  .then(data => console.log(data))
  .catch(error => console.error(error));
Enter fullscreen mode Exit fullscreen mode

Advanced Promise Methods

Promise.all

Promise.all is useful when you need to wait for multiple asynchronous operations to complete before proceeding.

Example:
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then(values => {
  console.log(values); // [3, 42, "foo"]
});
Enter fullscreen mode Exit fullscreen mode

Promise.race

Promise.race is beneficial when you need the result of the fastest operation.

Example:
const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then(value => {
  console.log(value); // "two"
});
Enter fullscreen mode Exit fullscreen mode

Simplifying Asynchronous Code with async/await

The async/await syntax allows you to write asynchronous code that looks synchronous, enhancing readability and reducing the complexity of chaining Promises.

Example:
async function fetchData() {
  return 'data';
}

async function processData() {
  try {
    const data = await fetchData();
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}

processData();
Enter fullscreen mode Exit fullscreen mode

Conclusion

While callbacks laid the groundwork for handling asynchronous operations in JavaScript, Promises have significantly improved the readability, maintainability, and error-handling capabilities of asynchronous code. Understanding how and when to use these tools effectively is crucial for modern JavaScript development. With Promises and the async/await syntax, developers can write cleaner, more manageable code, paving the way for more robust applications.

Top comments (2)

Collapse
 
oculus42 profile image
Samuel Rouse

Hey! I always love to see articles about Promises!

One thing grabbed my attention in the early numbered section:

Promise.race resolves as soon as one of the input Promises resolves

Promise.race() settles as soon as one of the input promises is settled, either resolved or rejected. This is an important distinction over resolves.

Promise.all() also rejects as soon as any promise in the array is rejected, which is where Promise.allSettled() comes in, should you need it.

Also, in your promise example, none of your functions depend on context so your promise chain can be simplified:

fetchData()
  .then(fetchMoreData)
  .then(fetchEvenMoreData)
  .then(console.log)
  .catch(console.error);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
dariansdev profile image
DariansDEV

Great catch! I need to tweak the post a bit to show how it can be done. the post was to gather concepts and the code should be tips and 'extra tips' like you caught. thank you so much for that tip