30 Days of JavaScript: Asynchronous JavaScript and Promises — Day 5

30 Days of Javascript Series by DopeThemes

Welcome back to Day 5 of our 30-day JavaScript tutorial series, where we unravel the mysteries of this powerful language one step at a time. Today, we venture into the realm of asynchronous programming, a cornerstone of JavaScript that enables responsive and high-performing web applications. In this tutorial, we’ll explore:

  • Understanding asynchronous programming
  • Callbacks, Promises, and async/await
  • Fetch API and making HTTP requests

Together, we’ll gain a solid grasp of asynchronous JavaScript, empowering you to create seamless and engaging user experiences. So, let’s not waste any time—join me as we continue our JavaScript journey, and unlock the full potential of this versatile language.

Understanding Asynchronous Programming

Asynchronous programming helps manage time-consuming tasks, such as fetching data from an API or processing large datasets, without blocking the main thread. This paradigm allows for the execution of non-blocking code, keeping your application responsive even when handling resource-intensive tasks.

Callbacks

Callbacks are the earliest approach to handling asynchronous tasks in JavaScript. A callback is a function passed as an argument to another function, which is then executed at a later time. Let’s examine a simple example, starting with a beginner-level callback:

function printGreeting(callback) {
  setTimeout(() => {
    const greeting = 'Hello, world!';
    callback(greeting);
  }, 1000);
}

printGreeting((greeting) => {
  console.log(greeting);
});

For a more intermediate example, let’s fetch data using callbacks:

function fetchData(url, callback) {
  const xhr = new XMLHttpRequest();
  xhr.onreadystatechange = () => {
    if (xhr.readyState === 4) {
      if (xhr.status === 200) {
        callback(null, JSON.parse(xhr.responseText));
      } else {
        callback(new Error(`Request failed with status ${xhr.status}`));
      }
    }
  };
  xhr.open('GET', url);
  xhr.send();
}

fetchData('https://jsonplaceholder.typicode.com/todos/1', (error, data) => {
  if (error) {
    console.error('Error:', error);
  } else {
    console.log('Data:', data);
  }
});

Callbacks can lead to “callback hell” when dealing with multiple nested callbacks, resulting in hard-to-read and maintain code.

Promises

Promises offer a more advanced way to handle asynchronous tasks. A Promise represents a value that may not be available yet but will be at some point. Promises have three possible states:

  • Pending: The initial state; neither fulfilled nor rejected.
  • Fulfilled: The operation completed successfully, and the Promise has a resulting value.
  • Rejected: The operation failed, and the Promise has a reason for the failure.

Let’s take a look at a beginner-level example of using Promises:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const random = Math.random();
    if (random > 0.5) {
      resolve('Success!');
    } else {
      reject('Failure!');
    }
  }, 1000);
});

promise
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.error(error);
  });

For a more intermediate example, let’s rewrite our previous fetchData function to use Promises:

function fetchData(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(JSON.parse(xhr.responseText));
        } else {
          reject(new Error(`Request failed with status ${xhr.status}`));
        }
      }
    };
    xhr.open('GET', url);
    xhr.send();
  });
}

fetchData('https://jsonplaceholder.typicode.com/todos/1')
  .then((data) => {
    console.log('Data:', data);
  }).catch((error) => {
    console.error('Error:', error);
  });

Using Promises improves code readability, but you can still end up with long chains of .then() and .catch() methods.

Async/Await

The async/await pattern is a more recent addition to JavaScript and makes asynchronous code look and behave more like synchronous code. To use this pattern, you need to mark a function as async, which allows you to use the await keyword inside it. await pauses the execution of the function until the Promise is resolved or rejected.

Let’s start with a beginner-level example:

async function processData() {
  const data = await new Promise((resolve) => {
    setTimeout(() => {
      resolve('Hello, async/await!');
    }, 1000);
  });

  console.log(data);
}

processData();

For a more intermediate example, let’s rewrite our fetchData function once more, this time using async/await:

async function fetchData(url) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`Request failed with status ${response.status}`);
    }
    const data = await response.json();
    console.log('Data:', data);
  } catch (error) {
    console.error('Error:', error);
  }
}

fetchData('https://jsonplaceholder.typicode.com/todos/1');

Fetch API and Making HTTP Requests

The Fetch API is a modern and powerful way to make HTTP requests in JavaScript. It is built on Promises, making it easy to use with async/await.

Here’s a beginner-level example of using the Fetch API:

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then((response) => response.json())
  .then((data) => {
    console.log('Data:', data);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

For a more advanced example, let’s create a function that fetches data from an API, handles pagination, and combines the results:

async function fetchAllData(url, limit = 10) {
  let currentPage = 1;
  let hasMoreData = true;
  const allData = [];

  while (hasMoreData) {
    const response = await fetch(`${url}?_page=${currentPage}&_limit=${limit}`);
    const data = await response.json();

    allData.push(...data);
    currentPage++;

    if (data.length < limit) {
      hasMoreData = false;
    }
  }

  return allData;
}

fetchAllData('https://jsonplaceholder.typicode.com/todos')
  .then((data) => {
    console.log(`Fetched ${data.length} items`);
  })
  .catch((error) => {
    console.error('Error:', error);
  });
Conclusion

In conclusion, we’ve delved deep into asynchronous JavaScript and Promises, including understanding asynchronous programming, callbacks, Promises, async/await, and the Fetch API. By mastering these concepts and techniques, you’ll be better equipped to create responsive, efficient, and user-friendly web applications.

Now that you’ve gained a solid understanding of asynchronous JavaScript, it’s time to look forward to our next tutorial, where we will explore modern JavaScript features such as arrow functions and template literals, destructuring, spread and rest operators, and modules with import/export. These powerful tools will help you write cleaner, more efficient, and maintainable code.

Stay tuned as we continue to uncover the full potential of JavaScript and enhance your web development skills. The journey has just begun!

Next: 30 Days of JavaScript: Modern JavaScript (ES6+) — Day 6


Stay in the loop with our web development newsletter - no spam, just juicy updates! Join us now. Join our web development newsletter to stay updated on the latest industry news, trends, and tips. No spam, just juicy updates delivered straight to your inbox. Don't miss out - sign up now!


We’ve tried our best to explain everything thoroughly, even though there’s so much information out there. If you found our writing helpful, we’d really appreciate it if you could buy us a coffee as a token of support.

Also, if you’re interested in learning more about WordPress, Javascript, HTML, CSS, and programming in general, you can subscribe to our MailChimp for some extra insights.

Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.