javascript

examples

examples.js
/**
 * ========================================
 * 11.3 Fetch API and HTTP - Examples
 * ========================================
 *
 * Comprehensive examples of making HTTP requests with the Fetch API.
 * Note: Most examples require an internet connection and a test API.
 */

/**
 * EXAMPLE 1: Basic GET Request
 *
 * The simplest form of fetch - retrieving data.
 */

// Promise syntax
function basicFetchWithPromise(url) {
  fetch(url)
    .then((response) => {
      console.log('Status:', response.status);
      console.log('OK:', response.ok);
      return response.json();
    })
    .then((data) => {
      console.log('Data:', data);
    })
    .catch((error) => {
      console.error('Error:', error);
    });
}

// Async/await syntax (preferred)
async function basicFetchWithAsync(url) {
  try {
    const response = await fetch(url);
    console.log('Status:', response.status);

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

    const data = await response.json();
    console.log('Data:', data);
    return data;
  } catch (error) {
    console.error('Fetch failed:', error);
    throw error;
  }
}

// Test with: basicFetchWithAsync('https://jsonplaceholder.typicode.com/posts/1')

/**
 * EXAMPLE 2: POST Request with JSON Body
 *
 * Sending data to a server.
 */

async function postJSON(url, data) {
  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json',
      },
      body: JSON.stringify(data),
    });

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

    return await response.json();
  } catch (error) {
    console.error('POST failed:', error);
    throw error;
  }
}

// Usage:
// postJSON('https://jsonplaceholder.typicode.com/posts', {
//     title: 'Hello',
//     body: 'World',
//     userId: 1
// }).then(console.log);

/**
 * EXAMPLE 3: All HTTP Methods
 *
 * Template functions for each HTTP method.
 */

const httpMethods = {
  async get(url) {
    const response = await fetch(url);
    return response.json();
  },

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

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

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

  async delete(url) {
    const response = await fetch(url, { method: 'DELETE' });
    return response.ok;
  },
};

/**
 * EXAMPLE 4: Query Parameters
 *
 * Building URLs with query strings.
 */

function buildURL(baseUrl, params) {
  const url = new URL(baseUrl);

  Object.entries(params).forEach(([key, value]) => {
    if (value !== undefined && value !== null) {
      url.searchParams.append(key, value);
    }
  });

  return url.toString();
}

async function fetchWithParams(baseUrl, params) {
  const url = buildURL(baseUrl, params);
  console.log('Fetching:', url);

  const response = await fetch(url);
  return response.json();
}

// Usage:
// fetchWithParams('https://api.example.com/search', {
//     query: 'javascript',
//     page: 1,
//     limit: 10
// });

/**
 * EXAMPLE 5: Request Headers
 *
 * Working with HTTP headers.
 */

async function fetchWithHeaders(url, customHeaders = {}) {
  // Create headers object
  const headers = new Headers({
    Accept: 'application/json',
    'Accept-Language': 'en-US',
    ...customHeaders,
  });

  const response = await fetch(url, { headers });

  // Reading response headers
  console.log('Response Headers:');
  response.headers.forEach((value, key) => {
    console.log(`  ${key}: ${value}`);
  });

  // Common header access
  console.log('Content-Type:', response.headers.get('content-type'));
  console.log('Content-Length:', response.headers.get('content-length'));

  return response.json();
}

/**
 * EXAMPLE 6: Authentication
 *
 * Common authentication patterns.
 */

// Basic Authentication
async function fetchWithBasicAuth(url, username, password) {
  const credentials = btoa(`${username}:${password}`);

  return fetch(url, {
    headers: {
      Authorization: `Basic ${credentials}`,
    },
  });
}

// Bearer Token Authentication
async function fetchWithBearerToken(url, token) {
  return fetch(url, {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });
}

// API Key Authentication
async function fetchWithApiKey(url, apiKey, headerName = 'X-API-Key') {
  return fetch(url, {
    headers: {
      [headerName]: apiKey,
    },
  });
}

/**
 * EXAMPLE 7: Error Handling
 *
 * Comprehensive error handling for fetch.
 */

class HTTPError extends Error {
  constructor(response) {
    super(`HTTP Error: ${response.status} ${response.statusText}`);
    this.name = 'HTTPError';
    this.status = response.status;
    this.statusText = response.statusText;
    this.response = response;
  }
}

async function fetchWithErrorHandling(url, options = {}) {
  let response;

  try {
    response = await fetch(url, options);
  } catch (error) {
    // Network error (offline, DNS failure, etc.)
    if (error.name === 'TypeError') {
      throw new Error('Network error: Unable to reach server');
    }
    throw error;
  }

  // HTTP error (4xx, 5xx)
  if (!response.ok) {
    const error = new HTTPError(response);

    // Try to parse error message from response
    try {
      const errorBody = await response.json();
      error.message = errorBody.message || error.message;
      error.details = errorBody;
    } catch {
      // Response wasn't JSON
    }

    throw error;
  }

  return response;
}

// Usage with specific error handling
async function demonstrateErrorHandling(url) {
  try {
    const response = await fetchWithErrorHandling(url);
    return await response.json();
  } catch (error) {
    if (error instanceof HTTPError) {
      switch (error.status) {
        case 401:
          console.log('Unauthorized - please log in');
          break;
        case 403:
          console.log('Forbidden - insufficient permissions');
          break;
        case 404:
          console.log('Resource not found');
          break;
        case 429:
          console.log('Too many requests - please slow down');
          break;
        case 500:
          console.log('Server error - try again later');
          break;
        default:
          console.log('HTTP Error:', error.status);
      }
    } else {
      console.log('Network or other error:', error.message);
    }
    throw error;
  }
}

/**
 * EXAMPLE 8: Request Timeout with AbortController
 *
 * Cancel requests that take too long.
 */

async function fetchWithTimeout(url, options = {}, timeout = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal,
    });
    clearTimeout(timeoutId);
    return response;
  } catch (error) {
    clearTimeout(timeoutId);
    if (error.name === 'AbortError') {
      throw new Error(`Request timeout after ${timeout}ms`);
    }
    throw error;
  }
}

// Modern approach using AbortSignal.timeout (newer browsers)
async function fetchWithTimeoutModern(url, options = {}, timeout = 5000) {
  try {
    return await fetch(url, {
      ...options,
      signal: AbortSignal.timeout(timeout),
    });
  } catch (error) {
    if (error.name === 'TimeoutError') {
      throw new Error(`Request timeout after ${timeout}ms`);
    }
    throw error;
  }
}

/**
 * EXAMPLE 9: Cancellable Requests
 *
 * Allow user to cancel in-flight requests.
 */

function createCancellableRequest(url, options = {}) {
  const controller = new AbortController();

  const promise = fetch(url, {
    ...options,
    signal: controller.signal,
  });

  return {
    promise,
    cancel: () => controller.abort(),
    signal: controller.signal,
  };
}

// Usage in a search component
class SearchComponent {
  constructor() {
    this.currentRequest = null;
  }

  async search(query) {
    // Cancel previous request if still pending
    if (this.currentRequest) {
      this.currentRequest.cancel();
    }

    this.currentRequest = createCancellableRequest(
      `https://api.example.com/search?q=${encodeURIComponent(query)}`
    );

    try {
      const response = await this.currentRequest.promise;
      const results = await response.json();
      return results;
    } catch (error) {
      if (error.name === 'AbortError') {
        console.log('Search cancelled');
        return null;
      }
      throw error;
    } finally {
      this.currentRequest = null;
    }
  }
}

/**
 * EXAMPLE 10: Retry Logic
 *
 * Automatically retry failed requests.
 */

async function fetchWithRetry(url, options = {}, config = {}) {
  const {
    retries = 3,
    delay = 1000,
    backoff = 2,
    retryOn = [500, 502, 503, 504],
  } = config;

  let lastError;

  for (let attempt = 0; attempt < retries; attempt++) {
    try {
      const response = await fetch(url, options);

      // Check if we should retry this status
      if (!response.ok && retryOn.includes(response.status)) {
        throw new Error(`HTTP ${response.status}`);
      }

      return response;
    } catch (error) {
      lastError = error;
      console.log(`Attempt ${attempt + 1} failed:`, error.message);

      if (attempt < retries - 1) {
        const waitTime = delay * Math.pow(backoff, attempt);
        console.log(`Waiting ${waitTime}ms before retry...`);
        await new Promise((r) => setTimeout(r, waitTime));
      }
    }
  }

  throw new Error(`Failed after ${retries} attempts: ${lastError.message}`);
}

/**
 * EXAMPLE 11: File Upload
 *
 * Uploading files with FormData.
 */

async function uploadFile(file, additionalData = {}) {
  const formData = new FormData();
  formData.append('file', file);

  // Add additional fields
  Object.entries(additionalData).forEach(([key, value]) => {
    formData.append(key, value);
  });

  const response = await fetch('https://api.example.com/upload', {
    method: 'POST',
    body: formData,
    // Don't set Content-Type - browser sets it with boundary
  });

  if (!response.ok) {
    throw new Error('Upload failed');
  }

  return response.json();
}

// Upload with progress (using XMLHttpRequest as fetch doesn't support upload progress)
function uploadWithProgress(file, onProgress) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    const formData = new FormData();
    formData.append('file', file);

    xhr.upload.addEventListener('progress', (event) => {
      if (event.lengthComputable) {
        const percent = (event.loaded / event.total) * 100;
        onProgress(percent);
      }
    });

    xhr.addEventListener('load', () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(JSON.parse(xhr.responseText));
      } else {
        reject(new Error(`Upload failed: ${xhr.status}`));
      }
    });

    xhr.addEventListener('error', () => reject(new Error('Network error')));
    xhr.open('POST', 'https://api.example.com/upload');
    xhr.send(formData);
  });
}

/**
 * EXAMPLE 12: Download with Progress
 *
 * Track download progress using streams.
 */

async function downloadWithProgress(url, onProgress) {
  const response = await fetch(url);
  const contentLength = response.headers.get('content-length');
  const total = parseInt(contentLength, 10);

  const reader = response.body.getReader();
  const chunks = [];
  let received = 0;

  while (true) {
    const { done, value } = await reader.read();

    if (done) break;

    chunks.push(value);
    received += value.length;

    if (total) {
      const percent = (received / total) * 100;
      onProgress(percent, received, total);
    }
  }

  // Combine chunks into single array
  const blob = new Blob(chunks);
  return blob;
}

// Usage:
// downloadWithProgress('https://example.com/large-file.zip', (percent) => {
//     console.log(`Downloaded: ${percent.toFixed(1)}%`);
// });

/**
 * EXAMPLE 13: Parallel Requests
 *
 * Fetch multiple resources simultaneously.
 */

async function fetchAll(urls) {
  const responses = await Promise.all(urls.map((url) => fetch(url)));

  // Check all responses
  responses.forEach((response, index) => {
    if (!response.ok) {
      console.warn(`Request to ${urls[index]} failed: ${response.status}`);
    }
  });

  // Parse all as JSON
  return Promise.all(responses.map((response) => response.json()));
}

// With error handling - get all that succeed
async function fetchAllSettled(urls) {
  const results = await Promise.allSettled(
    urls.map(async (url) => {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      return response.json();
    })
  );

  return results.map((result, index) => ({
    url: urls[index],
    status: result.status,
    data: result.status === 'fulfilled' ? result.value : null,
    error: result.status === 'rejected' ? result.reason : null,
  }));
}

/**
 * EXAMPLE 14: Sequential Requests
 *
 * Execute requests one after another.
 */

async function fetchSequential(urls) {
  const results = [];

  for (const url of urls) {
    const response = await fetch(url);
    const data = await response.json();
    results.push(data);
  }

  return results;
}

// With dependency - each request uses data from previous
async function fetchWithDependencies(initialUrl) {
  // First request
  const userResponse = await fetch(initialUrl);
  const user = await userResponse.json();

  // Second request uses first result
  const postsResponse = await fetch(`/users/${user.id}/posts`);
  const posts = await postsResponse.json();

  // Third request uses second result
  const commentsPromises = posts.map((post) =>
    fetch(`/posts/${post.id}/comments`).then((r) => r.json())
  );
  const comments = await Promise.all(commentsPromises);

  return { user, posts, comments };
}

/**
 * EXAMPLE 15: API Client Factory
 *
 * Create a reusable API client with common configuration.
 */

function createAPIClient(baseURL, defaultHeaders = {}) {
  async function request(endpoint, options = {}) {
    const url = new URL(endpoint, baseURL);

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

    if (!response.ok) {
      const error = new Error(`HTTP ${response.status}`);
      error.status = response.status;
      try {
        error.data = await response.json();
      } catch {}
      throw error;
    }

    // Return null for 204 No Content
    if (response.status === 204) return null;

    return response.json();
  }

  return {
    get: (endpoint, options) =>
      request(endpoint, { ...options, method: 'GET' }),
    post: (endpoint, data, options) =>
      request(endpoint, {
        ...options,
        method: 'POST',
        body: JSON.stringify(data),
      }),
    put: (endpoint, data, options) =>
      request(endpoint, {
        ...options,
        method: 'PUT',
        body: JSON.stringify(data),
      }),
    patch: (endpoint, data, options) =>
      request(endpoint, {
        ...options,
        method: 'PATCH',
        body: JSON.stringify(data),
      }),
    delete: (endpoint, options) =>
      request(endpoint, { ...options, method: 'DELETE' }),
  };
}

// Usage:
// const api = createAPIClient('https://api.example.com', {
//     'Authorization': 'Bearer token123'
// });
//
// await api.get('/users');
// await api.post('/users', { name: 'John' });
// await api.put('/users/1', { name: 'Jane' });
// await api.delete('/users/1');

/**
 * EXAMPLE 16: Response Caching
 *
 * Simple in-memory cache for API responses.
 */

class CachedFetch {
  constructor(options = {}) {
    this.cache = new Map();
    this.ttl = options.ttl || 5 * 60 * 1000; // 5 minutes default
  }

  generateKey(url, options = {}) {
    return `${options.method || 'GET'}-${url}`;
  }

  isExpired(entry) {
    return Date.now() > entry.expires;
  }

  async fetch(url, options = {}) {
    const key = this.generateKey(url, options);
    const cached = this.cache.get(key);

    // Return cached if valid
    if (cached && !this.isExpired(cached)) {
      console.log('Cache hit:', url);
      return cached.data;
    }

    // Fetch fresh data
    console.log('Cache miss:', url);
    const response = await fetch(url, options);
    const data = await response.json();

    // Cache the result
    this.cache.set(key, {
      data,
      expires: Date.now() + this.ttl,
    });

    return data;
  }

  invalidate(url, options = {}) {
    const key = this.generateKey(url, options);
    this.cache.delete(key);
  }

  clear() {
    this.cache.clear();
  }
}

/**
 * EXAMPLE 17: Request Queue
 *
 * Limit concurrent requests to prevent overwhelming the server.
 */

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

  async add(fetchFn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ fetchFn, resolve, reject });
      this.process();
    });
  }

  async process() {
    if (this.running >= this.maxConcurrent || this.queue.length === 0) {
      return;
    }

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

    try {
      const result = await fetchFn();
      resolve(result);
    } catch (error) {
      reject(error);
    } finally {
      this.running--;
      this.process();
    }
  }
}

// Usage:
// const queue = new RequestQueue(2);
//
// urls.forEach(url => {
//     queue.add(() => fetch(url).then(r => r.json()))
//         .then(data => console.log('Loaded:', url));
// });

/**
 * EXAMPLE 18: Streaming JSON
 *
 * Parse large JSON responses incrementally.
 */

async function streamLargeJSON(url, onItem) {
  const response = await fetch(url);
  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  let buffer = '';
  let inString = false;
  let braceCount = 0;
  let objectStart = -1;

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });

    // Parse objects from buffer
    for (let i = 0; i < buffer.length; i++) {
      const char = buffer[i];

      // Handle strings (to avoid counting braces in strings)
      if (char === '"' && buffer[i - 1] !== '\\') {
        inString = !inString;
      }

      if (!inString) {
        if (char === '{') {
          if (braceCount === 0) objectStart = i;
          braceCount++;
        } else if (char === '}') {
          braceCount--;
          if (braceCount === 0 && objectStart !== -1) {
            const jsonStr = buffer.substring(objectStart, i + 1);
            try {
              const obj = JSON.parse(jsonStr);
              onItem(obj);
            } catch (e) {
              // Invalid JSON, skip
            }
            objectStart = -1;
          }
        }
      }
    }

    // Keep unprocessed data in buffer
    if (objectStart !== -1) {
      buffer = buffer.substring(objectStart);
      objectStart = 0;
    } else {
      buffer = '';
    }
  }
}

console.log('Fetch API examples loaded!');
console.log(
  'Try: basicFetchWithAsync("https://jsonplaceholder.typicode.com/posts/1")'
);
Examples - JavaScript Tutorial | DeepML