İçeriğe geç
JavaScript

JavaScript Fetch API: HTTP İstekleri Rehberi

Tarık Tunç
JavaScript Fetch API: HTTP İstekleri Rehberi

JavaScript Fetch API: HTTP İstekleri Rehberi

Fetch API, tarayıcılarda HTTP istekleri yapmak için modern, Promise tabanlı bir arayüzdür. XMLHttpRequest'in yerini alan Fetch, daha temiz sözdizimi, stream desteği ve güçlü yapılandırma seçenekleri sunar. Bu rehberde GET/POST isteklerinden, dosya yükleme, timeout yönetimi ve interceptor pattern'lerine kadar her şeyi inceliyoruz.

1. Temel GET ve POST İstekleri

Fetch API'nin temel kullanımı basit ve sezgiseldir. Her fetch() çağrısı bir Promise döner ve yanıt nesnesi üzerinden veriye erişilir.

// GET isteği
async function kullanicilariGetir() {
  const yanit = await fetch("https://api.example.com/users");

  if (!yanit.ok) {
    throw new Error(`HTTP Hatası: ${yanit.status} ${yanit.statusText}`);
  }

  const kullanicilar = await yanit.json();
  return kullanicilar;
}

// POST isteği
async function kullaniciOlustur(kullanici) {
  const yanit = await fetch("https://api.example.com/users", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${token}`
    },
    body: JSON.stringify(kullanici)
  });

  if (!yanit.ok) {
    const hata = await yanit.json();
    throw new Error(hata.mesaj || `HTTP ${yanit.status}`);
  }

  return yanit.json();
}

// Kullanım
const yeniKullanici = await kullaniciOlustur({
  ad: "Tarık",
  email: "tarik@example.com",
  rol: "gelistirici"
});

// Diğer HTTP metotları
async function kullaniciGuncelle(id, veri) {
  return fetch(`https://api.example.com/users/${id}`, {
    method: "PUT", // veya "PATCH"
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(veri)
  });
}

async function kullaniciSil(id) {
  return fetch(`https://api.example.com/users/${id}`, {
    method: "DELETE"
  });
}

2. Yanıt Türleri ve İşleme

Response nesnesi, farklı veri formatlarını işlemek için çeşitli metotlar sunar. Her metot Promise döner ve body stream'ini tüketir (bir kez çağrılabilir).

const yanit = await fetch(url);

// JSON verisi
const jsonVeri = await yanit.json();

// Düz metin
const metin = await yanit.text();

// Blob (resim, dosya)
const blob = await yanit.blob();
const resimUrl = URL.createObjectURL(blob);

// ArrayBuffer (ikili veri)
const buffer = await yanit.arrayBuffer();

// FormData
const formVeri = await yanit.formData();

// Yanıt başlıkları
console.log(yanit.headers.get("Content-Type"));
console.log(yanit.headers.get("X-Total-Count"));

// Tüm başlıkları okuma
for (const [anahtar, deger] of yanit.headers) {
  console.log(`${anahtar}: ${deger}`);
}

// Response klonlama (body'yi birden fazla kez okumak için)
const yanitKlon = yanit.clone();
const jsonKopya = await yanit.json();
const metinKopya = await yanitKlon.text();

// Yanıt meta bilgileri
console.log(yanit.status);      // 200
console.log(yanit.statusText);  // "OK"
console.log(yanit.ok);          // true (200-299)
console.log(yanit.url);         // Gerçek URL (redirect sonrası)
console.log(yanit.redirected);  // Redirect oldu mu?
console.log(yanit.type);        // "basic", "cors", "opaque"
İpucu: fetch() yalnızca ağ hatalarında reject olur. HTTP 404 veya 500 gibi durumlar hata olarak değerlendirilmez — response.ok kontrolü yapmanız gerekir. Bu, axios gibi kütüphanelerden farklıdır.

3. AbortController ile Timeout ve İptal

Fetch API, AbortController ile istek iptali ve timeout mekanizması sunar.

// Temel istek iptali
const controller = new AbortController();

const yanit = fetch("https://api.example.com/buyuk-veri", {
  signal: controller.signal
});

// 5 saniye sonra iptal et
setTimeout(() => controller.abort(), 5000);

try {
  const veri = await yanit;
} catch (hata) {
  if (hata.name === "AbortError") {
    console.log("İstek iptal edildi");
  }
}

// Timeout yardımcı fonksiyonu
async function fetchWithTimeout(url, options = {}, timeoutMs = 5000) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const yanit = await fetch(url, {
      ...options,
      signal: controller.signal
    });
    return yanit;
  } catch (hata) {
    if (hata.name === "AbortError") {
      throw new Error(`İstek zaman aşımına uğradı (${timeoutMs}ms)`);
    }
    throw hata;
  } finally {
    clearTimeout(timeout);
  }
}

// Birden fazla isteği tek controller ile iptal
const topluController = new AbortController();

const istekler = [
  fetch("/api/a", { signal: topluController.signal }),
  fetch("/api/b", { signal: topluController.signal }),
  fetch("/api/c", { signal: topluController.signal })
];

// Tümünü iptal
topluController.abort();

4. Dosya Yükleme ve İlerleme Takibi

// FormData ile dosya yükleme
async function dosyaYukle(dosya) {
  const formData = new FormData();
  formData.append("dosya", dosya);
  formData.append("aciklama", "Profil resmi");

  const yanit = await fetch("/api/upload", {
    method: "POST",
    body: formData
    // Content-Type otomatik ayarlanır (boundary ile)
  });

  return yanit.json();
}

// Birden fazla dosya
async function cokluYukleme(dosyalar) {
  const formData = new FormData();
  Array.from(dosyalar).forEach((dosya, i) => {
    formData.append(`dosya_${i}`, dosya);
  });

  return fetch("/api/upload-multiple", {
    method: "POST",
    body: formData
  });
}

// İndirme ilerlemesi (ReadableStream)
async function indirmeIlerleme(url) {
  const yanit = await fetch(url);
  const toplam = parseInt(yanit.headers.get("Content-Length") || "0");
  let indirilen = 0;

  const reader = yanit.body.getReader();
  const parcalar = [];

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

    parcalar.push(value);
    indirilen += value.length;

    const yuzde = toplam ? Math.round((indirilen / toplam) * 100) : 0;
    console.log(`İndirme: ${yuzde}% (${indirilen}/${toplam} byte)`);
  }

  const blob = new Blob(parcalar);
  return blob;
}
Uyarı: Dosya yükleme sırasında Content-Type header'ını manuel olarak "multipart/form-data" olarak ayarlamayın. FormData kullanıyorsanız tarayıcı bunu boundary bilgisi ile birlikte otomatik olarak ayarlar.

5. Fetch Wrapper ve Interceptor Pattern

// Özelleştirilmiş fetch wrapper
class HttpClient {
  #baseUrl;
  #varsayilanHeaders;
  #interceptorlar;

  constructor(baseUrl, headers = {}) {
    this.#baseUrl = baseUrl;
    this.#varsayilanHeaders = {
      "Content-Type": "application/json",
      ...headers
    };
    this.#interceptorlar = { istek: [], yanit: [] };
  }

  interceptorEkle(tip, fn) {
    this.#interceptorlar[tip].push(fn);
  }

  async #istek(endpoint, options = {}) {
    let config = {
      ...options,
      headers: { ...this.#varsayilanHeaders, ...options.headers }
    };

    // İstek interceptor'ları
    for (const fn of this.#interceptorlar.istek) {
      config = await fn(config);
    }

    let yanit = await fetch(`${this.#baseUrl}${endpoint}`, config);

    // Yanıt interceptor'ları
    for (const fn of this.#interceptorlar.yanit) {
      yanit = await fn(yanit);
    }

    if (!yanit.ok) {
      const hata = await yanit.json().catch(() => ({}));
      throw Object.assign(new Error(hata.mesaj || `HTTP ${yanit.status}`), {
        status: yanit.status,
        veri: hata
      });
    }

    return yanit.json();
  }

  get(endpoint) { return this.#istek(endpoint); }
  post(endpoint, veri) {
    return this.#istek(endpoint, { method: "POST", body: JSON.stringify(veri) });
  }
  put(endpoint, veri) {
    return this.#istek(endpoint, { method: "PUT", body: JSON.stringify(veri) });
  }
  delete(endpoint) {
    return this.#istek(endpoint, { method: "DELETE" });
  }
}

// Kullanım
const api = new HttpClient("https://api.example.com");

// Token interceptor'ı
api.interceptorEkle("istek", async (config) => {
  const token = localStorage.getItem("token");
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

const kullanicilar = await api.get("/users");
Bilgi: Fetch API varsayılan olarak cross-origin isteklerde cookie göndermez. Cookie göndermek için credentials: "include" seçeneğini ekleyin. Same-origin isteklerde credentials: "same-origin" varsayılandır.

Sonuç

Fetch API, modern web uygulamalarında HTTP iletişiminin standart yoludur. Promise tabanlı yapısı, stream desteği, AbortController ile istek iptali ve esnek yapılandırma seçenekleri ile güçlü ve bakımı kolay HTTP istemcileri oluşturabilirsiniz. Fetch wrapper pattern'i ile kendi projenize özel interceptor'lar, hata yönetimi ve önbellekleme mekanizmaları ekleyerek geliştirme sürecinizi hızlandırın.

Kaynaklar

İlgili Yazılar