惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 叶小钗
D
Docker
GbyAI
GbyAI
Y
Y Combinator Blog
Google DeepMind News
Google DeepMind News
G
Google Developers Blog
P
Proofpoint News Feed
云风的 BLOG
云风的 BLOG
雷峰网
雷峰网
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Stack Overflow Blog
Stack Overflow Blog
WordPress大学
WordPress大学
小众软件
小众软件
Engineering at Meta
Engineering at Meta
酷 壳 – CoolShell
酷 壳 – CoolShell
I
InfoQ
B
Blog
H
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 聂微东
The GitHub Blog
The GitHub Blog
A
About on SuperTechFans
B
Blog RSS Feed
Microsoft Security Blog
Microsoft Security Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Mastering the Fetch API: Practical Patterns for Real-Worl...
Alex Chen · 2026-05-16 · via DEV Community

Alex Chen

Mastering the Fetch API: Practical Patterns for Real-World Apps

Fetch is more powerful than most developers realize.

The Basics (Quick Refresher)

// GET request
const response = await fetch('https://api.example.com/data');
const data = await response.json();

// POST request
const response = await fetch('https://api.example.com/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Alex', email: 'alex@example.com' }),
});

Enter fullscreen mode Exit fullscreen mode

A Robust Fetch Wrapper

class ApiClient {
  constructor(baseURL, options = {}) {
    this.baseURL = baseURL;
    this.timeout = options.timeout || 10000;
    this.headers = {
      'Content-Type': 'application/json',
      ...options.headers,
    };
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;

    // Abort controller for timeout
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), this.timeout);

    try {
      const response = await fetch(url, {
        ...options,
        headers: { ...this.headers, ...options.headers },
        signal: controller.signal,
      });

      clearTimeout(timeoutId);

      if (!response.ok) {
        throw new ApiError(response.status, await this.parseError(response));
      }

      return response;
    } catch (error) {
      clearTimeout(timeoutId);

      if (error.name === 'AbortError') {
        throw new Error(`Request timed out after ${this.timeout}ms`);
      }

      throw error;
    }
  }

  async get(endpoint, params = {}) {
    const query = new URLSearchParams(params).toString();
    const url = query ? `${endpoint}?${query}` : endpoint;
    const response = await this.request(url);
    return response.json();
  }

  async post(endpoint, data) {
    const response = await this.request(endpoint, {
      method: 'POST',
      body: JSON.stringify(data),
    });
    return response.json();
  }

  async put(endpoint, data) {
    const response = await this.request(endpoint, {
      method: 'PUT',
      body: JSON.stringify(data),
    });
    return response.json();
  }

  async delete(endpoint) {
    const response = await this.request(endpoint, { method: 'DELETE' });
    return response.status === 204 || response.json();
  }

  async parseError(response) {
    try {
      return await response.json();
    } catch {
      return { message: response.statusText };
    }
  }
}

class ApiError extends Error {
  constructor(statusCode, errorBody) {
    super(errorBody.message || `API Error ${statusCode}`);
    this.statusCode = statusCode;
    this.errorBody = errorBody;
  }
}

// Usage:
const api = new ApiClient('https://api.example.com', {
  timeout: 5000,
  headers: { Authorization: `Bearer ${token}` },
});

try {
  const users = await api.get('/users', { page: 1, limit: 20 });
  console.log(users);
} catch (error) {
  if (error instanceof ApiError) {
    console.error(`${error.statusCode}:`, error.errorBody);
  }
}

Enter fullscreen mode Exit fullscreen mode

Request Caching

class CachedFetch {
  #cache = new Map();

  async get(url, ttlMs = 60000) {
    const cached = this.#cache.get(url);

    if (cached && Date.now() - cached.timestamp < ttlMs) {
      return cached.data; // Return cache hit
    }

    const data = await fetch(url).then(r => r.json());
    this.#cache.set(url, { data, timestamp: Date.now() });
    return data;
  }

  invalidate(url) {
    this.#cache.delete(url);
  }

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

const cachedApi = new CachedFetch();

// These two calls only make ONE HTTP request
const [data1, data2] = await Promise.all([
  cachedApi.get('/api/data'),
  cachedApi.get('/api/data'), // Cache hit!
]);

Enter fullscreen mode Exit fullscreen mode

Retry Logic with Exponential Backoff

async function fetchWithRetry(url, options = {}) {
  const {
    maxRetries = 3,
    baseDelay = 1000,
    backoffFactor = 2,
    retryableStatuses = [408, 429, 500, 502, 503, 504],
  } = options;

  let lastError;

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

      if (response.ok || !retryableStatuses.includes(response.status)) {
        return response;
      }

      // Retryable server error — don't throw, retry instead
      lastError = new Error(`HTTP ${response.status}`);
    } catch (error) {
      lastError = error;
    }

    if (attempt < maxRetries) {
      const delay = baseDelay * Math.pow(backoffFactor, attempt);
      console.warn(`Attempt ${attempt + 1} failed. Retrying in ${delay}ms...`);
      await sleep(delay);
    }
  }

  throw lastError;
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Enter fullscreen mode Exit fullscreen mode

Concurrent Requests

// Parallel: All requests run simultaneously
async function loadDashboardData(userId) {
  const [user, posts, notifications, friends] = await Promise.all([
    fetch(`/api/users/${userId}`).then(r => r.json()),
    fetch(`/api/users/${userId}/posts`).then(r => r.json()),
    fetch(`/api/notifications`).then(r => r.json()),
    fetch(`/api/users/${userId}/friends`).then(r => r.json()),
  ]);

  return { user, posts, notifications, friends };
}

// Race: Use fastest successful response
async function fetchWithFallback(primaryUrl, fallbackUrl) {
  try {
    const result = await Promise.any([
      fetch(primaryUrl).then(r => r.json()),
      fetch(fallbackUrl).then(r => r.json()),
    ]);
    return result;
  } catch {
    throw new Error('All endpoints failed');
  }
}

// Batched: Process in groups to avoid overwhelming the server
async function batchFetch(urls, batchSize = 5) {
  const results = [];
  for (let i = 0; i < urls.length; i += batchSize) {
    const batch = urls.slice(i, i + batchSize);
    const batchResults = await Promise.all(
      batch.map(url => fetch(url).then(r => r.json()).catch(() => null))
    );
    results.push(...batchResults);
  }
  return results.filter(Boolean);
}

Enter fullscreen mode Exit fullscreen mode

Progress Tracking for Uploads

async function uploadWithProgress(file, url, onProgress) {
  const xhr = new XMLHttpRequest();

  return new Promise((resolve, reject) => {
    xhr.upload.addEventListener('progress', (e) => {
      if (e.lengthComputable) {
        const percent = Math.round((e.loaded / e.total) * 100);
        onProgress(percent, e.loaded, e.total);
      }
    });

    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', url);
    xhr.send(file);
  });
}

// Usage:
uploadWithProgress(file, '/api/upload', (percent, loaded, total) => {
  progressBar.value = percent;
  statusText.textContent = `${percent}% (${formatBytes(loaded)} / ${formatBytes(total)})`;
});

Enter fullscreen mode Exit fullscreen mode

File Download

async function downloadFile(url, filename) {
  const response = await fetch(url);

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

  const blob = await response.blob();

  // Create download link
  const link = document.createElement('a');
  link.href = URL.createObjectURL(blob);
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
  URL.revokeObjectURL(link.href); // Clean up memory
}

Enter fullscreen mode Exit fullscreen mode


What's your favorite fetch pattern? Anything I missed?

Follow @armorbreak for more JavaScript content.