Fetching Data with Async/Await in JavaScript

The fetch API is the modern, standard way to make network requests in the browser. While it’s based on Promises, using it with async/await syntax makes asynchronous code look clean, sequential, and much easier to read.

This guide will walk you through a practical example: fetching blog posts from a JSON placeholder API and dynamically adding them to the DOM.

The Goal

We will fetch a list of posts from an API. As the user scrolls to the bottom of the page, we will automatically fetch and display more posts, creating an “infinite scroll” effect.

The HTML Setup

First, we need a basic HTML structure. This includes a container for our posts and a simple loading animation that we’ll show while fetching new data.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Blog</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <h1>My Blog Posts</h1>
    <div id="posts-container"></div>
    <div class="loader">
        <div class="circle"></div>
        <div class="circle"></div>
        <div class="circle"></div>
    </div>
    <script src="script.js"></script>
</body>
</html>

(Note: The CSS for the loader and post styling is not shown here for brevity.)

The JavaScript Logic

Now, let’s write the JavaScript to fetch and display the posts.

1. Initial Setup

We’ll start by getting references to our DOM elements and setting up some variables to manage the API pagination.

// filepath: script.js
const postsContainer = document.getElementById('posts-container');
const loading = document.querySelector('.loader');

let limit = 5; // Number of posts to fetch per request
let page = 1;  // The current page of posts

// ... rest of the code

2. Fetching Posts from the API

We’ll create an async function called getPosts. Using async allows us to use the await keyword inside it, which pauses the function’s execution until a Promise is resolved.

  • await fetch(...): We wait for the network request to complete. It returns a Response object.
  • await res.json(): We wait for the Response body to be parsed as JSON.
// filepath: script.js
// ...existing code...

// Fetch posts from API
async function getPosts() {
 try {
  const res = await fetch(
   `https://jsonplaceholder.typicode.com/posts?_limit=${limit}&_page=${page}`
  );

  if (!res.ok) {
   throw new Error(`HTTP error! status: ${res.status}`);
  }

  const data = await res.json();
  return data;
 } catch (error) {
  console.error("Could not fetch posts:", error);
  // Optionally, display an error message to the user in the DOM
  return []; // Return an empty array on error
 }
}

// ... rest of the code

Notice the try...catch block. This is crucial for handling network errors or if the API request fails.

3. Displaying Posts in the DOM

Next, another async function, showPosts, will call getPosts() and then iterate over the returned data to create and append new post elements to our container.

// filepath: script.js
// ...existing code...

// Show posts in DOM
async function showPosts() {
 const posts = await getPosts();

 posts.forEach(post => {
  const postEl = document.createElement('div');
  postEl.classList.add('post');
  postEl.innerHTML = `
   <div class="number">${post.id}</div>
   <div class="post-info">
    <h2 class="post-title">${post.title}</h2>
    <p class="post-body">${post.body}</p>
   </div>
  `;
  postsContainer.appendChild(postEl);
 });
}

// ... rest of the code

4. Implementing Infinite Scroll

To create the infinite scroll effect, we’ll show a loading animation, fetch posts, hide the animation, and then listen for a scroll event. When the user scrolls near the bottom, we’ll increment the page number and fetch more posts.

// filepath: script.js
// ...existing code...

// Show loader & fetch more posts
async function showLoading() {
 loading.classList.add('show');

 // Increment page for the next fetch
 page++;
 await showPosts();

 loading.classList.remove('show');
}

// Initial post load
showPosts();

// Event listener for scrolling
window.addEventListener('scroll', () => {
 const { scrollTop, scrollHeight, clientHeight } = document.documentElement;

 if (scrollTop + clientHeight >= scrollHeight - 5) {
  showLoading();
 }
});

Final Code

Here is the complete, optimized script.js file:

// filepath: script.js
const postsContainer = document.getElementById('posts-container');
const loading = document.querySelector('.loader');

let limit = 5;
let page = 1;

// Fetch posts from API
async function getPosts() {
 try {
  const res = await fetch(
   `https://jsonplaceholder.typicode.com/posts?_limit=${limit}&_page=${page}`
  );

  if (!res.ok) {
   throw new Error(`HTTP error! status: ${res.status}`);
  }

  const data = await res.json();
  return data;
 } catch (error) {
  console.error("Could not fetch posts:", error);
  return [];
 }
}

// Show posts in DOM
async function showPosts() {
 const posts = await getPosts();

 posts.forEach(post => {
  const postEl = document.createElement('div');
  postEl.classList.add('post');
  postEl.innerHTML = `
   <div class="number">${post.id}</div>
   <div class="post-info">
    <h2 class="post-title">${post.title}</h2>
    <p class="post-body">${post.body}</p>
   </div>
  `;
  postsContainer.appendChild(postEl);
 });
}

// Show loader & fetch more posts
async function showLoading() {
 loading.classList.add('show');

 // Increment page for the next fetch
 page++;
 await showPosts();

 loading.classList.remove('show');
}

// Show initial posts
showPosts();

// Listen for scroll event
window.addEventListener('scroll', () => {
 const { scrollTop, scrollHeight, clientHeight } = document.documentElement;

 // Check if we have scrolled to the bottom of the page
 if (clientHeight + scrollTop >= scrollHeight - 5) {
  showLoading();
 }
});