Making HTTP Requests in JavaScript

intermediate
Published

July 1, 2024

This guide covers both the modern fetch API and the legacy XMLHttpRequest object for making HTTP requests in JavaScript. We’ll explore how to use both approaches and their various features.

The Fetch API

Basic Usage

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

// Using async/await
async function fetchData() {
    try {
        const response = await fetch('https://api.example.com/data');
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error('Error:', error);
    }
}

Request Methods and Options

// POST request with JSON data
async function postData(url, data) {
    const response = await fetch(url, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer your-token-here'
        },
        body: JSON.stringify(data)
    });
    return response.json();
}

// PUT request
async function updateData(url, data) {
    const response = await fetch(url, {
        method: 'PUT',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
    });
    return response.json();
}

// DELETE request
async function deleteData(url) {
    const response = await fetch(url, {
        method: 'DELETE'
    });
    return response.status === 204;
}

Handling Different Response Types

// JSON response
async function fetchJSON() {
    const response = await fetch('/api/data');
    return response.json();
}

// Text response
async function fetchText() {
    const response = await fetch('/api/text');
    return response.text();
}

// Binary data (Blob)
async function fetchImage() {
    const response = await fetch('/api/image');
    const blob = await response.blob();
    const imageUrl = URL.createObjectURL(blob);
    return imageUrl;
}

// Form data
async function fetchFormData() {
    const response = await fetch('/api/form');
    return response.formData();
}

Error Handling and Response Checking

async function fetchWithErrorHandling(url) {
    try {
        const response = await fetch(url);
        
        // Check if response is ok (status in 200-299 range)
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        // Check content type
        const contentType = response.headers.get('content-type');
        if (!contentType || !contentType.includes('application/json')) {
            throw new TypeError("Expected JSON response");
        }
        
        return await response.json();
    } catch (error) {
        console.error('Error:', error);
        throw error;
    }
}

Sending Form Data

// Sending form data
async function submitForm(formData) {
    const response = await fetch('/api/submit', {
        method: 'POST',
        body: formData // FormData object
    });
    return response.json();
}

// Sending file
async function uploadFile(file) {
    const formData = new FormData();
    formData.append('file', file);
    
    const response = await fetch('/api/upload', {
        method: 'POST',
        body: formData
    });
    return response.json();
}

XMLHttpRequest Object

Basic Usage

function makeRequest(method, url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        
        xhr.open(method, url);
        
        xhr.onload = function() {
            if (xhr.status >= 200 && xhr.status < 300) {
                resolve(xhr.response);
            } else {
                reject({
                    status: xhr.status,
                    statusText: xhr.statusText
                });
            }
        };
        
        xhr.onerror = function() {
            reject({
                status: xhr.status,
                statusText: xhr.statusText
            });
        };
        
        xhr.send();
    });
}

POST Request with XMLHttpRequest

function postData(url, data) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        
        xhr.open('POST', url);
        xhr.setRequestHeader('Content-Type', 'application/json');
        
        xhr.onload = function() {
            if (xhr.status >= 200 && xhr.status < 300) {
                resolve(JSON.parse(xhr.response));
            } else {
                reject({
                    status: xhr.status,
                    statusText: xhr.statusText
                });
            }
        };
        
        xhr.onerror = function() {
            reject({
                status: xhr.status,
                statusText: xhr.statusText
            });
        };
        
        xhr.send(JSON.stringify(data));
    });
}

Progress Monitoring with XMLHttpRequest

function uploadWithProgress(file, progressCallback) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        const formData = new FormData();
        formData.append('file', file);
        
        xhr.open('POST', '/api/upload');
        
        xhr.upload.onprogress = function(event) {
            if (event.lengthComputable) {
                const percentComplete = (event.loaded / event.total) * 100;
                progressCallback(percentComplete);
            }
        };
        
        xhr.onload = function() {
            if (xhr.status >= 200 && xhr.status < 300) {
                resolve(JSON.parse(xhr.response));
            } else {
                reject({
                    status: xhr.status,
                    statusText: xhr.statusText
                });
            }
        };
        
        xhr.onerror = function() {
            reject({
                status: xhr.status,
                statusText: xhr.statusText
            });
        };
        
        xhr.send(formData);
    });
}

// Usage
uploadWithProgress(file, (progress) => {
    console.log(`Upload progress: ${progress}%`);
}).then(response => {
    console.log('Upload complete:', response);
}).catch(error => {
    console.error('Upload failed:', error);
});

Practical Examples

1. Retry Mechanism

async function fetchWithRetry(url, options = {}, maxRetries = 3) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            const response = await fetch(url, options);
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            return await response.json();
        } catch (error) {
            if (attempt === maxRetries) throw error;
            
            // Wait before retrying (exponential backoff)
            const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
            await new Promise(resolve => setTimeout(resolve, delay));
        }
    }
}

2. Request Queue

class RequestQueue {
    constructor(concurrency = 3) {
        this.concurrency = concurrency;
        this.queue = [];
        this.running = 0;
    }
    
    async enqueue(request) {
        return new Promise((resolve, reject) => {
            this.queue.push({
                request,
                resolve,
                reject
            });
            this.processQueue();
        });
    }
    
    async processQueue() {
        if (this.running >= this.concurrency || this.queue.length === 0) {
            return;
        }
        
        this.running++;
        const { request, resolve, reject } = this.queue.shift();
        
        try {
            const response = await fetch(request);
            const data = await response.json();
            resolve(data);
        } catch (error) {
            reject(error);
        } finally {
            this.running--;
            this.processQueue();
        }
    }
}

// Usage
const queue = new RequestQueue(2);
const requests = [
    new Request('https://api.example.com/1'),
    new Request('https://api.example.com/2'),
    new Request('https://api.example.com/3')
];

requests.forEach(request => {
    queue.enqueue(request)
        .then(data => console.log('Request complete:', data))
        .catch(error => console.error('Request failed:', error));
});

Best Practices

  1. Error Handling
    • Always include error handling
    • Check response.ok with fetch
    • Validate response content type
  2. Request Timeouts
    • Implement timeout mechanisms
    • Use AbortController for fetch
    • Set xhr.timeout for XMLHttpRequest
  3. Security
    • Always validate and sanitize data
    • Use HTTPS
    • Implement proper CORS headers
  4. Performance
    • Use request queuing for multiple requests
    • Implement retry mechanisms
    • Cache responses when appropriate