fetch() artık her modern tarayıcıda ve Node.js 18+'da yerleşik olarak bulunmaktadır. XMLHttpRequest'in yerini aldı ve çoğu projede Axios'a duyulan ihtiyacı ortadan kaldırdı. Ancak her eğitimin gösterdiği varsayılan kullanım — fetch(url).then(r => r.json()) — hata işlemeyi atlar, zaman aşımı yoktur ve gerçek bir üretim ortamında çöker. Bu rehber gerçekten işe yarayan kalıpları kapsamaktadır.

Temel Konular — GET ve POST

fetch() bir Response nesnesiyle çözümlenen bir Promise döndürür. GET isteği basittir:

js
const response = await fetch('https://api.example.com/products');
const products = await response.json();

JSON gövdeli bir POST isteği biraz daha fazla kurulum gerektirir:

js
const newProduct = {
  name:     'Wireless Keyboard',
  price:    79.99,
  category: 'electronics'
};

const response = await fetch('https://api.example.com/products', {
  method:  'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${authToken}`
  },
  body: JSON.stringify(newProduct)
});

const created = await response.json();
console.log(`Created product with id: ${created.id}`);
Yaygın hata: POST isteklerinde Content-Type: application/json'u unutmak. Bu olmadan birçok sunucu çerçevesi gövdeyi ayrıştırmaz ve 400 Bad Request veya yararlı hata mesajı içermeyen boş bir istek gövdesi alırsınız.

İki Aşamalı Hata Kontrolü — response.ok

Bu, özümsenmesi gereken en önemli kalıptır. fetch() yalnızca ağ hatalarında (bağlantı yok, DNS hatası, CORS engellemesi) Promise'ini reddeder. 404, 401 veya 500 yanıtı Promise'i yine de çözümler — ancak response.ok değeri false olarak ayarlanmış şekilde. Bunu kontrol etmezseniz hata yanıtlarını sessizce response.json()'a iletirsiniz:

js
// ❌ Broken — 404 and 500 responses appear to succeed
async function getProduct(id) {
  const response = await fetch(`/api/products/${id}`);
  return await response.json(); // parses the error body as if it were data
}

// ✅ Correct — check response.ok before parsing
async function getProduct(id) {
  const response = await fetch(`/api/products/${id}`);

  if (!response.ok) {
    // Try to get the error message from the body if it's JSON
    const errorBody = await response.json().catch(() => null);
    const message   = errorBody?.message ?? `HTTP ${response.status}: ${response.statusText}`;
    throw new Error(message);
  }

  return await response.json();
}

response.ok, 200–299 durum kodları için true'dur. Diğer her şey — 301 yönlendirmeleri (otomatik takip edilmiyorsa), 400 hataları, 500 hataları — bunu false olarak ayarlar. Ayrıştırmadan önce her zaman kontrol edin.

AbortController — Zaman Aşımları ve İptal

fetch()'in yerleşik zaman aşımı yoktur. Sunucu aktarım ortasında yanıt vermeyi bırakırsa bir istek süresiz olarak askıda kalabilir. Çözüm AbortController'dır:

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

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

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

    return await response.json();
  } catch (err) {
    if (err.name === 'AbortError') {
      throw new Error(`Request timed out after ${timeoutMs}ms`);
    }
    throw err;
  } finally {
    clearTimeout(timeoutId);
  }
}

// Usage
try {
  const data = await fetchWithTimeout('/api/products', {}, 5000);
} catch (err) {
  console.error(err.message); // "Request timed out after 5000ms"
}

AbortController, bir kullanıcı başka bir sayfaya gittiğinde veya önceki tamamlanmadan yeni bir arama yaptığında devam eden istekleri iptal etmek için de kullanışlıdır:

js
let searchController = null;

async function searchProducts(query) {
  // Cancel any previous search request
  if (searchController) {
    searchController.abort();
  }

  searchController = new AbortController();

  try {
    const response = await fetch(
      `/api/products/search?q=${encodeURIComponent(query)}`,
      { signal: searchController.signal }
    );
    return await response.json();
  } catch (err) {
    if (err.name !== 'AbortError') throw err;
    return null; // request was cancelled — that's OK
  }
}

Üstel Geri Çekilme ile Yeniden Deneme

Ağ istekleri geçici olarak başarısız olur — bir denemede gelen 503, bir sonrakinde genellikle başarılı olur. Üstel geri çekilme standart stratejidir: aşırı yüklenmiş bir sunucuyu yormamak için yeniden denemeler arasında giderek daha uzun süre beklemek:

js
async function fetchWithRetry(url, options = {}, retries = 3, baseDelayMs = 500) {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      const response = await fetch(url, options);

      // Don't retry client errors (4xx) — they won't fix themselves
      if (response.status >= 400 && response.status < 500) {
        throw new Error(`Client error ${response.status} — not retrying`);
      }

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

      return await response.json();

    } catch (err) {
      const isLastAttempt = attempt === retries;

      if (isLastAttempt || err.message.includes('Client error')) {
        throw err;
      }

      // Exponential backoff with jitter: 500ms, 1000ms, 2000ms + random
      const delay = baseDelayMs * 2 ** (attempt - 1) + Math.random() * 100;
      console.warn(`Attempt ${attempt} failed, retrying in ${Math.round(delay)}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// Usage
const products = await fetchWithRetry('/api/products', {}, 3, 500);
Jitter önemlidir: Gecikmeye Math.random() * 100 eklemek "thundering herd"i önler — binlerce istemcinin bir sunucu aksaklığından sonra tam olarak aynı anda yeniden denemesi. Küçük rastgele fark, büyük güvenilirlik faydası.

Bir Interceptor Kalıbı — fetch() Sarmalamak

Axios, interceptor kavramını yaygınlaştırdı: her istekten önce ve her yanıttan sonra çalışan bir kanca. Aynı şeyi fetch()'in etrafına ince bir sarmalayıcı olarak inşa edebilirsiniz:

js
// api.js — your project's fetch wrapper
const API_BASE = 'https://api.example.com';

function getAuthToken() {
  return localStorage.getItem('authToken');
}

async function apiFetch(path, options = {}) {
  const url = `${API_BASE}${path}`;

  // Request interceptor — add auth header to every request
  const headers = {
    'Content-Type': 'application/json',
    ...options.headers
  };

  const token = getAuthToken();
  if (token) headers['Authorization'] = `Bearer ${token}`;

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

  // Response interceptor — handle auth expiry globally
  if (response.status === 401) {
    logout(); // token expired — redirect to login
    throw new Error('Session expired');
  }

  if (!response.ok) {
    const body = await response.json().catch(() => ({}));
    throw new Error(body.message ?? `API error ${response.status}`);
  }

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

  return response.json();
}

// Usage — clean, no repeated boilerplate
const products = await apiFetch('/products');
const created  = await apiFetch('/products', {
  method: 'POST',
  body:   JSON.stringify({ name: 'New Product', price: 29.99 })
});

Bu kalıp, kimlik doğrulamayı, hata işlemeyi ve temel URL yapılandırmasını merkezi hale getirir. Kod tabanınızdaki her API çağrısı aynı davranışı ücretsiz alır. Gereksinimler değiştiğinde — yeni bir başlık eklemek veya JWT'den oturum çerezlerine geçmek gibi — tek bir yeri güncellersiniz.

Farklı Yanıt Türlerini İşleme

Her API JSON döndürmez. fetch(), tüm yaygın yanıt türleri için yöntemlere sahiptir:

js
// JSON (most common)
const data = await response.json();

// Plain text (CSV, HTML, logs)
const csvText = await response.text();

// Binary data — download a file
const blob       = await response.blob();
const blobUrl    = URL.createObjectURL(blob);
const downloadLink = document.createElement('a');
downloadLink.href     = blobUrl;
downloadLink.download = 'export.csv';
downloadLink.click();
URL.revokeObjectURL(blobUrl); // clean up

// ArrayBuffer — for WebAssembly or typed arrays
const buffer     = await response.arrayBuffer();
const byteArray  = new Uint8Array(buffer);

// Form data (multipart responses)
const formData = await response.formData();

CORS — Geliştiricilerin Bilmesi Gerekenler

CORS (Çapraz Kaynak Kaynak Paylaşımı), sunucu tarafından değil tarayıcı tarafından uygulanır. app.example.com adresindeki ön ucunuz api.example.com'dan veri çektiğinde, tarayıcı bir Origin başlığı gönderir ve izin için yanıtı kontrol eder. İstemci tarafından kontrol edebildikleriniz şunlardır:

js
// credentials: 'include' — send cookies/session tokens cross-origin
// The server must also respond with Access-Control-Allow-Credentials: true
const response = await fetch('https://api.example.com/account', {
  credentials: 'include'
});

// credentials: 'same-origin' — default, only sends credentials to same origin
// credentials: 'omit' — never send credentials (useful for public CDN requests)

// mode: 'no-cors' — fire-and-forget for cross-origin requests
// You get a "opaque" response — no status, no body, no error
await fetch('https://analytics.example.com/event', {
  method: 'POST',
  mode:   'no-cors',
  body:   JSON.stringify({ event: 'page_view' })
});
// Use for analytics pings where you don't need the response

CORS hataları alıyorsanız, düzeltme neredeyse her zaman sunucudadır: sunucunun yanıt başlıklarına Access-Control-Allow-Origin eklemesi gerekir. MDN'nin CORS rehberi nasıl yapılandırılacağına ilişkin en kapsamlı referanstır.

Faydalı Araçlar

Fetch yanıtlarını hata ayıklarken JSON Formatter API yüklerini okunabilir hale getirir ve JSON Validator hatalı biçimlendirilmiş yanıtları yakalar. Sorgu parametrelerini güvenli bir şekilde kodlamak için JSON URL Encode işinizi görür. MDN Using Fetch rehberi tüm fetch seçenekleri ve yanıt yöntemleri için en iyi tek referanstır.

Sonuç

fetch(), JavaScript'te HTTP istekleri için sağlam bir temeldir, ancak varsayılanlar üretimde ihtiyaç duyduğunuz her şeyi dışarıda bırakır. Ayrıştırmadan önce her zaman response.ok'u kontrol edin. AbortController ile zaman aşımları ekleyin. Kimlik doğrulama başlıklarını, hata normalleştirmeyi ve 401 yönlendirmelerini tek bir yerde ele almak için ince bir sarmalayıcı işlev oluşturun. Geçici olarak başarısız olabilecek istekler için üstel geri çekilme ile yeniden deneme mantığı ekleyin. Bu dört alışkanlık, demo kodu ile üretim kodu arasındaki farktır.