Advanced Usage of Fetch API in JavaScript

A comprehensive guide to using the Fetch API for efficient network requests, including parallel fetching, error handling, and integration with async/await

Last updated: 2025-01-05

Hello, JavaScript developers! Today, we're diving deep into the Fetch API, a powerful interface for making HTTP requests. We'll explore how to use it effectively, especially in the context of parallel and concurrent programming.

Introduction to Fetch API

The Fetch API provides a powerful and flexible feature for making network requests. It's a more modern and promise-based alternative to XMLHttpRequest.

Basic usage:

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

Parallel Requests with Fetch API

One of the great advantages of the Fetch API is the ability to make multiple requests in parallel easily.

Using Promise.all()

async function fetchMultipleUrls(urls) {
  try {
    const responses = await Promise.all(
      urls.map(url => fetch(url))
    );
    const data = await Promise.all(
      responses.map(response => response.json())
    );
    return data;
  } catch (error) {
    console.error('Error fetching data:', error);
  }
}

const urls = [
  'https://api.example.com/users',
  'https://api.example.com/posts',
  'https://api.example.com/comments'
];

fetchMultipleUrls(urls)
  .then(results => console.log('All data:', results));

This approach sends all requests simultaneously and waits for all of them to complete.

Error Handling and Request Customization

Proper error handling is crucial when working with network requests. Here's an example of how to handle errors and customize requests:

async function fetchWithErrorHandling(url, options = {}) {
  try {
    const response = await fetch(url, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        ...options.headers,
      },
    });

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

    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Fetch error:', error);
    throw error; // Re-throw to allow handling in calling function
  }
}

// Usage
fetchWithErrorHandling('https://api.example.com/data', {
  method: 'POST',
  body: JSON.stringify({ key: 'value' }),
})
  .then(data => console.log('Data:', data))
  .catch(error => console.error('Error in main code:', error));

Concurrent Requests with Async/Await

Here's how you can make concurrent requests using async/await and manage their results:

async function fetchConcurrentData() {
  try {
    const [users, posts, comments] = await Promise.all([
      fetch('https://api.example.com/users').then(res => res.json()),
      fetch('https://api.example.com/posts').then(res => res.json()),
      fetch('https://api.example.com/comments').then(res => res.json())
    ]);

    console.log('Users:', users);
    console.log('Posts:', posts);
    console.log('Comments:', comments);

    // Process the data concurrently
    const processedData = await Promise.all([
      processUsers(users),
      processPosts(posts),
      processComments(comments)
    ]);

    return processedData;
  } catch (error) {
    console.error('Error in concurrent fetching:', error);
  }
}

async function processUsers(users) {
  // Simulate processing
  await new Promise(resolve => setTimeout(resolve, 100));
  return users.map(user => ({ ...user, processed: true }));
}

async function processPosts(posts) {
  // Simulate processing
  await new Promise(resolve => setTimeout(resolve, 150));
  return posts.map(post => ({ ...post, processed: true }));
}

async function processComments(comments) {
  // Simulate processing
  await new Promise(resolve => setTimeout(resolve, 200));
  return comments.map(comment => ({ ...comment, processed: true }));
}

fetchConcurrentData().then(results => console.log('Processed data:', results));

Implementing a Fetch Queue

Sometimes, you might want to limit the number of concurrent requests. Here's an implementation of a fetch queue:

class FetchQueue {
  constructor(concurrency = 3) {
    this.concurrency = concurrency;
    this.queue = [];
    this.running = 0;
  }

  enqueue(url, options = {}) {
    return new Promise((resolve, reject) => {
      this.queue.push({ url, options, resolve, reject });
      this.dequeue();
    });
  }

  dequeue() {
    if (this.running >= this.concurrency || this.queue.length === 0) {
      return;
    }

    const { url, options, resolve, reject } = this.queue.shift();
    this.running++;

    fetch(url, options)
      .then(response => response.json())
      .then(resolve)
      .catch(reject)
      .finally(() => {
        this.running--;
        this.dequeue();
      });
  }
}

// Usage
const fetchQueue = new FetchQueue(2); // Allow 2 concurrent requests

const urls = [
  'https://api.example.com/data1',
  'https://api.example.com/data2',
  'https://api.example.com/data3',
  'https://api.example.com/data4',
  'https://api.example.com/data5'
];

Promise.all(urls.map(url => fetchQueue.enqueue(url)))
  .then(results => console.log('All results:', results))
  .catch(error => console.error('Error in queue:', error));

This queue ensures that only a specified number of fetch requests are running concurrently.

Fetch API with Timeout

Sometimes, you might want to set a timeout for your fetch requests. Here's how you can implement this:

function fetchWithTimeout(url, options = {}, timeout = 5000) {
  return Promise.race([
    fetch(url, options),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Request timed out')), timeout)
    )
  ]);
}

// Usage
fetchWithTimeout('https://api.example.com/data', {}, 3000)
  .then(response => response.json())
  .then(data => console.log('Data:', data))
  .catch(error => console.error('Error or timeout:', error));

Conclusion

The Fetch API is a powerful tool for making network requests in JavaScript. When combined with modern JavaScript features like async/await and Promise methods, it enables efficient parallel and concurrent data fetching.

Remember these key points:

  1. Use Promise.all() for parallel requests when you need all requests to complete.
  2. Implement proper error handling for each fetch request.
  3. Consider using a queue system for controlling concurrent requests.
  4. Timeout your requests when dealing with potentially slow APIs.

By mastering these techniques, you can create more efficient and responsive web applications that handle complex data fetching scenarios with ease.

Additional Resources

  1. MDN Web Docs - Fetch API
  2. JavaScript.info - Fetch
  3. Google Developers - Introduction to fetch()