Fortgeschritten252025-01-15

JavaScript Fetch API & AJAX - HTTP Requests einfach gemacht

Lerne wie du mit der Fetch API HTTP Requests sendest, Daten von APIs lädst und mit dem Server kommunizierst. Inklusive Error Handling und praktischen Beispielen.

#javascript#fetch#ajax#api#http#rest

JavaScript Fetch API & AJAX

Die Fetch API ist die moderne Methode, um HTTP-Requests zu senden und mit APIs zu kommunizieren. Sie ersetzt das alte XMLHttpRequest (AJAX).

Was ist Fetch?

Fetch API - Ein einfaches Beispiel
// GET Request - Daten abrufen
fetch('https://api.example.com/users')
  .then(response => response.json())
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('Fehler:', error);
  });

// Oder mit async/await (moderner!)
async function fetchUsers() {
  try {
    const response = await fetch('https://api.example.com/users');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Fehler:', error);
  }
}

fetchUsers();

GET Requests - Daten abrufen

Der Standard-Request ist ein GET Request.

GET Request mit fetch()
// Einfacher GET Request
async function getUsers() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');

    // Response Status prüfen
    if (!response.ok) {
      throw new Error(`HTTP Error: ${response.status}`);
    }

    const users = await response.json();
    console.log('Users:', users);
    return users;
  } catch (error) {
    console.error('Fehler beim Laden:', error);
    throw error;
  }
}

getUsers();

Response Objekt verstehen

Response Properties
async function analyzeResponse() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users/1');

  console.log('Status:', response.status);         // 200
  console.log('Status Text:', response.statusText); // 'OK'
  console.log('OK?:', response.ok);                 // true (bei 200-299)
  console.log('Headers:', response.headers);
  console.log('URL:', response.url);

  // Response Body parsen
  const data = await response.json(); // Als JSON
  // const text = await response.text(); // Als Text
  // const blob = await response.blob(); // Als Blob (Bilder, etc.)

  console.log('Data:', data);
}

analyzeResponse();

POST Requests - Daten senden

POST Requests senden Daten zum Server.

POST Request - Daten erstellen
async function createUser(userData) {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(userData)
    });

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

    const newUser = await response.json();
    console.log('User erstellt:', newUser);
    return newUser;
  } catch (error) {
    console.error('Fehler:', error);
    throw error;
  }
}

// Verwenden
createUser({
  name: 'Anna Schmidt',
  email: 'anna@example.com',
  age: 25
});

PUT & PATCH Requests - Daten aktualisieren

PUT = Vollständiges Update, PATCH = Teilweises Update

PUT & PATCH Requests
// PUT - Vollständiges Update
async function updateUser(userId, userData) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(userData)
  });

  return await response.json();
}

// PATCH - Teilweises Update
async function patchUser(userId, updates) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`, {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(updates)
  });

  return await response.json();
}

// Verwenden
updateUser(1, {
  name: 'Anna Schmidt',
  email: 'anna@example.com',
  age: 26
});

patchUser(1, {
  age: 26  // Nur age updaten
});

DELETE Requests - Daten löschen

DELETE Request
async function deleteUser(userId) {
  try {
    const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`, {
      method: 'DELETE'
    });

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

    console.log('User gelöscht:', userId);
    return true;
  } catch (error) {
    console.error('Fehler beim Löschen:', error);
    return false;
  }
}

deleteUser(1);

Headers - HTTP Header setzen

Headers enthalten Meta-Informationen über den Request.

Custom Headers
async function fetchWithAuth() {
  const response = await fetch('https://api.example.com/protected', {
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer YOUR_TOKEN_HERE',
      'Accept': 'application/json',
      'X-Custom-Header': 'CustomValue'
    }
  });

  return await response.json();
}

// Oder mit Headers Object
async function fetchWithHeadersObject() {
  const headers = new Headers();
  headers.append('Content-Type', 'application/json');
  headers.append('Authorization', 'Bearer TOKEN');

  const response = await fetch('https://api.example.com/data', {
    headers: headers
  });

  return await response.json();
}

Query Parameters - URL Parameter

Query Parameters hinzufügen
// ❌ Manuell (fehleranfällig)
const url = 'https://api.example.com/search?q=javascript&limit=10';

// ✅ Mit URLSearchParams (sicherer!)
const params = new URLSearchParams({
  q: 'javascript',
  limit: 10,
  sort: 'date'
});

const url = `https://api.example.com/search?${params.toString()}`;
console.log(url);
// 'https://api.example.com/search?q=javascript&limit=10&sort=date'

// In Fetch verwenden
async function search(query, limit = 10) {
  const params = new URLSearchParams({ q: query, limit });
  const response = await fetch(`https://api.example.com/search?${params}`);
  return await response.json();
}

search('javascript', 20);

Error Handling - Fehlerbehandlung

Wichtig: fetch() wirft nur bei Netzwerkfehlern einen Error, nicht bei HTTP-Errors (404, 500, etc.)!

Richtiges Error Handling
async function fetchWithErrorHandling(url) {
  try {
    const response = await fetch(url);

    // HTTP Errors manuell prüfen!
    if (!response.ok) {
      // 404, 500, etc.
      throw new Error(`HTTP Error ${response.status}: ${response.statusText}`);
    }

    const data = await response.json();
    return { success: true, data };
  } catch (error) {
    // Netzwerkfehler oder JSON Parse Error
    console.error('Fehler:', error);
    return { success: false, error: error.message };
  }
}

// Verwenden
const result = await fetchWithErrorHandling('https://api.example.com/data');

if (result.success) {
  console.log('Daten:', result.data);
} else {
  console.error('Fehler:', result.error);
}

Verschiedene Error Types

Detailliertes Error Handling
async function fetchWithDetailedErrors(url) {
  try {
    const response = await fetch(url);

    if (!response.ok) {
      // Unterschiedliche HTTP Errors behandeln
      switch (response.status) {
        case 400:
          throw new Error('Bad Request - Ungültige Anfrage');
        case 401:
          throw new Error('Unauthorized - Nicht autorisiert');
        case 403:
          throw new Error('Forbidden - Zugriff verweigert');
        case 404:
          throw new Error('Not Found - Ressource nicht gefunden');
        case 500:
          throw new Error('Server Error - Interner Serverfehler');
        default:
          throw new Error(`HTTP Error: ${response.status}`);
      }
    }

    return await response.json();
  } catch (error) {
    if (error instanceof TypeError) {
      // Netzwerkfehler
      console.error('Netzwerkfehler:', error);
    } else {
      // HTTP oder andere Fehler
      console.error('Fehler:', error.message);
    }
    throw error;
  }
}

Timeout - Request abbrechen

AbortController erlaubt es, Requests abzubrechen.

Fetch mit Timeout
async function fetchWithTimeout(url, timeout = 5000) {
  // AbortController erstellen
  const controller = new AbortController();
  const signal = controller.signal;

  // Timeout Timer
  const timeoutId = setTimeout(() => {
    controller.abort(); // Request abbrechen
  }, timeout);

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

    clearTimeout(timeoutId); // Timer stoppen

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

    return await response.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      console.error('Request Timeout!');
      throw new Error('Request dauerte zu lange');
    }
    throw error;
  }
}

// Verwenden
try {
  const data = await fetchWithTimeout('https://api.example.com/slow', 3000);
  console.log(data);
} catch (error) {
  console.error('Fehler:', error.message);
}

Praktische Beispiele

Beispiel 1: User Liste laden und anzeigen

User Liste mit Loading State
async function loadAndDisplayUsers() {
  const container = document.getElementById('users');

  // Loading anzeigen
  container.innerHTML = '<p>Laden...</p>';

  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');

    if (!response.ok) {
      throw new Error('Fehler beim Laden');
    }

    const users = await response.json();

    // HTML generieren
    const html = users.map(user => `
      <div class="user-card">
        <h3>${user.name}</h3>
        <p>Email: ${user.email}</p>
        <p>Stadt: ${user.address.city}</p>
      </div>
    `).join('');

    container.innerHTML = html;
  } catch (error) {
    container.innerHTML = `<p class="error">Fehler: ${error.message}</p>`;
  }
}

loadAndDisplayUsers();

Beispiel 2: Suchfunktion mit Debouncing

Live Search mit API
let searchTimeout;

async function searchUsers(query) {
  // Debouncing - warte 300ms bevor Request
  clearTimeout(searchTimeout);

  searchTimeout = setTimeout(async () => {
    if (!query) {
      displayResults([]);
      return;
    }

    try {
      const response = await fetch(`https://api.example.com/search?q=${query}`);
      const results = await response.json();
      displayResults(results);
    } catch (error) {
      console.error('Suchfehler:', error);
    }
  }, 300);
}

function displayResults(results) {
  const container = document.getElementById('results');

  if (results.length === 0) {
    container.innerHTML = '<p>Keine Ergebnisse</p>';
    return;
  }

  container.innerHTML = results.map(item => `
    <div class="result">${item.name}</div>
  `).join('');
}

// Event Listener
document.getElementById('search').addEventListener('input', (e) => {
  searchUsers(e.target.value);
});

Beispiel 3: Form Submission

Formular mit Fetch absenden
async function handleFormSubmit(event) {
  event.preventDefault();

  const form = event.target;
  const submitBtn = form.querySelector('button[type="submit"]');
  const statusDiv = document.getElementById('status');

  // Form Data sammeln
  const formData = {
    name: form.name.value,
    email: form.email.value,
    message: form.message.value
  };

  // Button deaktivieren
  submitBtn.disabled = true;
  submitBtn.textContent = 'Senden...';
  statusDiv.textContent = '';

  try {
    const response = await fetch('https://api.example.com/contact', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(formData)
    });

    if (!response.ok) {
      throw new Error('Fehler beim Senden');
    }

    const result = await response.json();

    // Erfolg!
    statusDiv.textContent = 'Nachricht gesendet!';
    statusDiv.className = 'success';
    form.reset();
  } catch (error) {
    // Fehler
    statusDiv.textContent = `Fehler: ${error.message}`;
    statusDiv.className = 'error';
  } finally {
    // Button wieder aktivieren
    submitBtn.disabled = false;
    submitBtn.textContent = 'Senden';
  }
}

// Event Listener
document.getElementById('contact-form').addEventListener('submit', handleFormSubmit);

Beispiel 4: File Upload

File Upload mit Fetch
async function uploadFile(file) {
  const formData = new FormData();
  formData.append('file', file);
  formData.append('title', file.name);

  try {
    const response = await fetch('https://api.example.com/upload', {
      method: 'POST',
      body: formData // KEIN Content-Type Header! Browser setzt automatisch
    });

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

    const result = await response.json();
    console.log('Upload erfolgreich:', result);
    return result;
  } catch (error) {
    console.error('Upload Fehler:', error);
    throw error;
  }
}

// Event Listener für File Input
document.getElementById('file-input').addEventListener('change', async (e) => {
  const file = e.target.files[0];

  if (!file) return;

  try {
    const result = await uploadFile(file);
    alert(`Datei hochgeladen: ${result.url}`);
  } catch (error) {
    alert('Upload fehlgeschlagen!');
  }
});

Request Options - Alle Optionen

Alle Fetch Options
const options = {
  method: 'POST',           // GET, POST, PUT, PATCH, DELETE

  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer TOKEN'
  },

  body: JSON.stringify({    // Nur bei POST/PUT/PATCH
    name: 'Anna'
  }),

  mode: 'cors',             // cors, no-cors, same-origin

  credentials: 'include',   // include, same-origin, omit
                             // include = Cookies mitsenden

  cache: 'default',         // default, no-store, reload, no-cache, force-cache

  redirect: 'follow',       // follow, error, manual

  referrer: 'client',       // no-referrer, client

  signal: controller.signal // AbortController Signal
};

const response = await fetch('https://api.example.com/data', options);

Fetch vs. Axios

Fetch ist eingebaut, Axios ist eine externe Library.

Fetch vs Axios Vergleich
// Fetch (eingebaut)
async function fetchExample() {
  try {
    const response = await fetch('https://api.example.com/users');

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

    const data = await response.json();
    return data;
  } catch (error) {
    console.error(error);
  }
}

// Axios (externe Library)
async function axiosExample() {
  try {
    const response = await axios.get('https://api.example.com/users');
    // response.data ist automatisch JSON!
    // HTTP Errors werfen automatisch Errors!
    return response.data;
  } catch (error) {
    console.error(error);
  }
}

// Vorteile Fetch:
// ✅ Eingebaut, keine Library nötig
// ✅ Modern und standardisiert

// Vorteile Axios:
// ✅ Automatisches JSON Parsing
// ✅ HTTP Errors werfen automatisch Errors
// ✅ Request/Response Interceptors
// ✅ Bessere Error Messages

📝 Quiz

Wann wirft fetch() einen Error?

Tipps & Tricks

Retry Logic implementieren

async function fetchWithRetry(url, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url);

      if (response.ok) {
        return await response.json();
      }

      // Nur bei Server Errors (500+) wiederholen
      if (response.status < 500) {
        throw new Error(\`HTTP Error: \${response.status}\`);
      }
    } catch (error) {
      if (i === maxRetries - 1) {
        throw error; // Letzter Versuch fehlgeschlagen
      }

      // Warte vor erneutem Versuch (exponential backoff)
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
    }
  }
}

Request Caching

const cache = new Map();

async function fetchWithCache(url, ttl = 60000) {
  // Cache prüfen
  const cached = cache.get(url);

  if (cached && Date.now() - cached.time < ttl) {
    return cached.data;
  }

  // Fetch neu
  const response = await fetch(url);
  const data = await response.json();

  // In Cache speichern
  cache.set(url, { data, time: Date.now() });

  return data;
}

Parallel Requests

// ❌ Sequentiell (langsam)
const users = await fetch('/api/users').then(r => r.json());
const posts = await fetch('/api/posts').then(r => r.json());

// ✅ Parallel (schnell!)
const [users, posts] = await Promise.all([
  fetch('/api/users').then(r => r.json()),
  fetch('/api/posts').then(r => r.json())
]);

Progress Tracking (File Upload)

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

  return new Promise((resolve, reject) => {
    xhr.upload.addEventListener('progress', (e) => {
      if (e.lengthComputable) {
        const percent = (e.loaded / e.total) * 100;
        console.log(\`Upload: \${percent.toFixed(0)}%\`);
      }
    });

    xhr.addEventListener('load', () => {
      resolve(JSON.parse(xhr.response));
    });

    xhr.addEventListener('error', () => {
      reject(new Error('Upload failed'));
    });

    const formData = new FormData();
    formData.append('file', file);

    xhr.open('POST', '/api/upload');
    xhr.send(formData);
  });
}

Häufige Fehler

Fehler 1: response.ok nicht prüfen

FALSCH:

const response = await fetch('https://api.example.com/data');
const data = await response.json(); // Fehler bei 404!

RICHTIG:

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

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

const data = await response.json();

Fehler 2: Body doppelt lesen

FALSCH:

const response = await fetch('https://api.example.com/data');
const data1 = await response.json();
const data2 = await response.json(); // Error: Already read!

RICHTIG:

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

Fehler 3: JSON.stringify vergessen

FALSCH:

fetch('https://api.example.com/users', {
  method: 'POST',
  body: { name: 'Anna' } // ❌ Objekt!
});

RICHTIG:

fetch('https://api.example.com/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Anna' }) // ✅ String!
});

Fehler 4: Content-Type bei FormData

FALSCH:

const formData = new FormData();
formData.append('file', file);

fetch('/upload', {
  method: 'POST',
  headers: {
    'Content-Type': 'multipart/form-data' // ❌ Falsch!
  },
  body: formData
});

RICHTIG:

const formData = new FormData();
formData.append('file', file);

fetch('/upload', {
  method: 'POST',
  // KEIN Content-Type Header! Browser setzt automatisch
  body: formData
});
🎯

Zusammenfassung

Du hast gelernt:

  • fetch() für HTTP Requests (GET, POST, PUT, DELETE)
  • ✅ Async/Await für sauberen Code
  • ✅ Response Object und .json() Parsing
  • ✅ Error Handling mit response.ok Check
  • ✅ Headers und Authorization
  • ✅ Query Parameters mit URLSearchParams
  • ✅ AbortController für Timeouts
  • ✅ FormData für File Uploads
  • ✅ Praktische Patterns (Retry, Caching, Debouncing)

Key Takeaways:

  • fetch() wirft nur bei Netzwerkfehlern Errors
  • response.ok manuell prüfen für HTTP Errors!
  • response.json() nur einmal aufrufen
  • JSON.stringify() für Request Body
  • AbortController für Timeouts/Abbrechen
  • FormData ohne Content-Type Header

Best Practices:

  • Immer Error Handling implementieren
  • Async/Await statt Promise Chains
  • Timeout mit AbortController
  • Loading States im UI anzeigen
  • Retry Logic für kritische Requests
  • Request Caching wo sinnvoll

Praktische Übungen

Übung 1: GET Request

Lade User-Daten von https://jsonplaceholder.typicode.com/users und zeige Namen in einer Liste an.

Übung 2: POST Request

Erstelle einen neuen Post mit:

{
  title: 'Mein Titel',
  body: 'Mein Inhalt',
  userId: 1
}

URL: https://jsonplaceholder.typicode.com/posts

Übung 3: Error Handling

Schreibe eine Funktion, die:

  • Daten von einer URL lädt
  • Bei Fehler 3x wiederholt
  • Error Message zurückgibt

Übung 4: Search mit Debouncing

Erstelle eine Suchfunktion die:

  • 300ms wartet bevor Request
  • Loading State anzeigt
  • Ergebnisse anzeigt

Übung 5: Real-World API

Baue eine kleine App mit:

  1. User Liste laden
  2. User Details anzeigen (onClick)
  3. User löschen (DELETE)
  4. Loading & Error States

API: https://jsonplaceholder.typicode.com/users

JavaScriptLektion 15 von 17
88% abgeschlossen
Lektion abgeschlossen!

Gut gemacht! 🎉

Du hast "JavaScript Fetch API & AJAX - HTTP Requests einfach gemacht" abgeschlossen

Artikel bewerten

0.0 (0 Bewertungen)

Bitte einloggen um zu bewerten