# JavaScript APIs & Promises - Anfänger Tutorial ## 📚 Inhaltsverzeichnis 1. [Promise Grundlagen](#promise-grundlagen) 2. [Async/Await](#asyncawait) 3. [Try/Catch Fehlerbehandlung](#trycatch-fehlerbehandlung) 4. [Promise Chaining mit .then()](#promise-chaining-mit-then) 5. [Fetch API](#fetch-api) 6. [Externe APIs nutzen](#externe-apis-nutzen) 7. [**Firebase Realtime Database**](#firebase-realtime-database) ⭐ **NEU** 8. [Praxisbeispiele](#praxisbeispiele) --- ## 🎯 Promise Grundlagen ### Was ist ein Promise? - **Versprechen** für ein zukünftiges Ergebnis - **Drei Zustände:** pending, fulfilled, rejected - **Asynchrone Operationen** ohne Callback-Hell ### Basis Promise Syntax ```javascript let promise = new Promise((resolve, reject) => { setTimeout(() => { if (success) { resolve("Erfolgreich!"); } else { reject("Fehler aufgetreten!"); } }, 1000); }); ``` ### Einfaches Beispiel ```javascript function getPromise() { return new Promise((resolve, reject) => { setTimeout(() => { let success = true; if (success) { resolve("Hat geklappt!"); } else { reject("Hat nicht geklappt!"); } }, 1000); }); } ``` --- ## 🔄 Async/Await ### Vorteile von Async/Await - **Sauberer Code** - keine Callback-Hell - **Leserlicher** als Promise-Chains - **Einfache Fehlerbehandlung** mit try/catch ### Syntax ```javascript async function meineFunktion() { try { let ergebnis = await getPromise(); console.log(ergebnis); } catch (error) { console.error(error); } } ``` ### Mehrere Promises abwarten ```javascript async function mehrerePromises() { try { await getPromise1(); await getPromise2(); await getPromise3(); console.log("Alle Promises abgeschlossen!"); } catch (error) { console.log("Ein Fehler ist aufgetreten:", error); } } ``` --- ## 🛡️ Try/Catch Fehlerbehandlung ### Warum Try/Catch? - **Fehler abfangen** ohne App-Crash - **Benutzerfreundliche** Fehlermeldungen - **Debugging** vereinfachen ### Beispiel mit mehreren Promises ```javascript async function usePromise() { try { await getPromise1(); await getPromise2(); await getPromise3(); console.log("Alle erfolgreich!"); } catch (error) { console.log("Fehler:", error); } finally { console.log("Immer ausgeführt"); } } ``` --- ## 🔗 Promise Chaining mit .then() ### Wann .then() verwenden? - **Sequentielle Abarbeitung** von Promises - **Verkettung** von Operationen - **Alternative** zu async/await ### Basis .then() Syntax ```javascript promise .then(result => { console.log("Erfolg:", result); return nextPromise(); }) .then(result => { console.log("Zweites Ergebnis:", result); }) .catch(error => { console.error("Fehler:", error); }); ``` ### Praxisbeispiel ```javascript function usePromise() { getGoodPromise() .then(result => { console.log(result); return getBadPromise(); }) .then(result => { console.log(result); }) .catch(error => { console.error(error); }); } ``` --- ## 🌐 Fetch API ### Was ist Fetch? - **Moderne Alternative** zu XMLHttpRequest - **Promise-basiert** für HTTP-Requests - **Einfacher** als traditionelle AJAX-Calls ### Basis Fetch Syntax ```javascript fetch(url) .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error(error)); ``` ### Lokale JSON-Datei laden ```javascript async function fetchDataJson() { try { let response = await fetch('db.json'); let data = await response.json(); console.log(data); } catch (error) { console.error('Fehler beim Laden:', error); } } ``` ### Text-Datei laden ```javascript async function fetchDataText() { try { let response = await fetch('h1.txt'); let text = await response.text(); // Wichtig: .text() statt .json() document.getElementById("content").innerHTML = text; } catch (error) { console.error('Fehler:', error); } } ``` --- ## 🍎 Externe APIs nutzen ### Fruityvice API - **Kostenlose API** für Fruchtdaten - **Keine Registrierung** erforderlich - **Basis-URL:** `https://www.fruityvice.com/api/fruit/` ### API Endpunkte - `GET /api/fruit/all` - Alle Früchte - `GET /api/fruit/{name}` - Spezifische Frucht - `GET /api/fruit/random` - Zufällige Frucht - `PUT /api/fruit` - Neue Frucht hinzufügen ### Einzelne Frucht abrufen ```javascript async function getFruit(fruitName) { try { let response = await fetch(`https://www.fruityvice.com/api/fruit/${fruitName}`); let fruitData = await response.json(); console.log(fruitData); return fruitData; } catch (error) { console.error('Frucht nicht gefunden:', error); } } // Verwendung getFruit('strawberry'); ``` ### Alle Früchte abrufen ```javascript async function getAllFruits() { try { let response = await fetch('https://www.fruityvice.com/api/fruit/all'); let fruits = await response.json(); console.log('Anzahl Früchte:', fruits.length); return fruits; } catch (error) { console.error('Fehler beim Laden aller Früchte:', error); } } ``` ### Zufällige Frucht ```javascript async function getRandomFruit() { try { let response = await fetch('https://www.fruityvice.com/api/fruit/random'); let randomFruit = await response.json(); console.log('Zufällige Frucht:', randomFruit.name); return randomFruit; } catch (error) { console.error('Fehler bei zufälliger Frucht:', error); } } ``` --- ## 🔥 Firebase Realtime Database ### Was ist Firebase Realtime Database? - **NoSQL Cloud-Datenbank** von Google - **Realtime-Synchronisation** zwischen Clients - **REST API** für einfache Integration - **Kostenlos** für kleine Projekte ### Setup und Konfiguration 1. **Firebase Projekt** erstellen: [console.firebase.google.com](https://console.firebase.google.com) 2. **Realtime Database** aktivieren 3. **Sicherheitsregeln** anpassen für Development: ```json { "rules": { ".read": true, ".write": true } } ``` ### Firebase URL Struktur ```javascript const BASE_URL = "https://PROJEKT-NAME-default-rtdb.REGION.firebasedatabase.app/"; // Beispiel: const BASE_URL = "https://da-porjectapi-training-default-rtdb.europe-west1.firebasedatabase.app/"; ``` ### 📖 READ - Daten laden (GET) ```javascript async function loadData(){ try { let response = await fetch(BASE_URL + ".json"); if (!response.ok) { throw new Error(`HTTP Error: ${response.status}`); } let responseToJson = await response.json(); console.log("Firebase data:", responseToJson); return responseToJson; } catch (error) { console.error("error:", error); } } // Spezifischen Pfad laden async function loadDataFromPath(path) { try { let response = await fetch(BASE_URL + path + ".json"); let data = await response.json(); return data; } catch (error) { console.error("Fehler beim Laden:", error); } } // Verwendung loadDataFromPath("users/user1"); // Lädt /users/user1 ``` ### ✍️ CREATE - Neue Daten hinzufügen (POST) ```javascript async function postData(path = "", data = {}) { try { let response = await fetch(BASE_URL + path + ".json", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(data) }); if (!response.ok) { throw new Error(`HTTP Error: ${response.status}`); } let responseToJson = await response.json(); console.log("Posted data response:", responseToJson); return responseToJson; } catch (error) { console.error("Error posting data:", error); } } // Verwendung postData("users", { name: "Max Mustermann", email: "max@example.com", age: 25 }); ``` ### 🔄 UPDATE - Daten überschreiben (PUT) ```javascript async function putData(path = "", data = {}) { try { let response = await fetch(BASE_URL + path + ".json", { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(data) }); if (!response.ok) { throw new Error(`HTTP Error: ${response.status}`); } let responseToJson = await response.json(); console.log("Data updated:", responseToJson); return responseToJson; } catch (error) { console.error("Error updating data:", error); } } // Verwendung putData("users/user1", { name: "Max Mustermann Updated", email: "max.new@example.com" }); ``` ### 🗑️ DELETE - Daten löschen (DELETE) ```javascript async function deleteData(path = "") { try { let response = await fetch(BASE_URL + path + ".json", { method: "DELETE" }); if (!response.ok) { throw new Error(`HTTP Error: ${response.status}`); } console.log(`Data at path "${path}" deleted successfully`); return true; } catch (error) { console.error("Error deleting data:", error); return false; } } // Verwendung deleteData("users/user1"); // Löscht /users/user1 ``` ### 🔧 PATCH - Teilweise Updates ```javascript async function patchData(path = "", data = {}) { try { let response = await fetch(BASE_URL + path + ".json", { method: "PATCH", headers: { "Content-Type": "application/json", }, body: JSON.stringify(data) }); if (!response.ok) { throw new Error(`HTTP Error: ${response.status}`); } let responseToJson = await response.json(); console.log("Data patched:", responseToJson); return responseToJson; } catch (error) { console.error("Error patching data:", error); } } // Verwendung - nur bestimmte Felder ändern patchData("users/user1", { age: 26 // Nur age wird geändert, rest bleibt }); ``` ### 📝 Vollständiges Firebase Beispiel #### index.html ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Firebase Storage</title> <script src="remoteStorage.js"></script> </head> <body onload="onloadFunc()"> <div id="content"></div> </body> </html> ``` #### remoteStorage.js ```javascript const BASE_URL = "https://da-porjectapi-training-default-rtdb.europe-west1.firebasedatabase.app/"; async function loadData(){ try { let response = await fetch(BASE_URL + ".json"); if (!response.ok) { throw new Error(`HTTP Error: ${response.status}`); } let responseToJson = await response.json(); console.log("Firebase data:", responseToJson); return responseToJson; } catch (error) { console.error("error:", error); } } async function postData(path = "", data = {}) { try { let response = await fetch(BASE_URL + path + ".json", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(data) }); let responseToJson = await response.json(); console.log("Posted data response:", responseToJson); return responseToJson; } catch (error) { console.error("Error posting data:", error); } } async function deleteData(path = "") { try { let response = await fetch(BASE_URL + path + ".json", { method: "DELETE" }); if (!response.ok) { throw new Error(`HTTP Error: ${response.status}`); } console.log(`Data at path "${path}" deleted successfully`); return true; } catch (error) { console.error("Error deleting data:", error); return false; } } function onloadFunc(){ console.log("App started..."); loadData(); postData("", {"banana": "rama"}); deleteData("/name/"); } ``` ### Firebase URL-Struktur verstehen ```javascript // Basis URL https://PROJEKT-default-rtdb.REGION.firebasedatabase.app/ // Pfade hinzufügen /users.json // Alle User /users/user1.json // Spezifischer User /users/user1/name.json // Nur Name von user1 /products/electronics.json // Elektronik-Produkte // Beispiele: loadData("users") // GET /users.json postData("users", userData) // POST /users.json putData("users/user1", data) // PUT /users/user1.json deleteData("users/user1") // DELETE /users/user1.json ``` ### Firebase vs. andere APIs | Feature | Firebase | REST API | GraphQL | |---------|----------|----------|---------| | **Setup** | Einfach | Mittel | Komplex | | **Realtime** | ✅ Ja | ❌ Nein | ✅ Ja | | **Offline** | ✅ Ja | ❌ Nein | ❌ Nein | | **Kosten** | Kostenlos* | Variiert | Variiert | | **Skalierung** | Automatisch | Manuell | Manuell | --- ## 💡 Praxisbeispiele ### Beispiel 1: Frucht-Suche ```javascript async function searchFruit() { let fruitName = document.getElementById('fruitInput').value; try { let response = await fetch(`https://www.fruityvice.com/api/fruit/${fruitName}`); if (!response.ok) { throw new Error('Frucht nicht gefunden'); } let fruit = await response.json(); displayFruit(fruit); } catch (error) { console.error('Fehler:', error); document.getElementById('result').innerHTML = 'Frucht nicht gefunden!'; } } function displayFruit(fruit) { let html = ` <h2>${fruit.name}</h2> <p>Familie: ${fruit.family}</p> <p>Kalorien: ${fruit.nutritions.calories}</p> <p>Zucker: ${fruit.nutritions.sugar}g</p> `; document.getElementById('result').innerHTML = html; } ``` ### Beispiel 2: Firebase Todo-App ```javascript // Todo hinzufügen async function addTodo(todoText) { let todoData = { text: todoText, completed: false, created: Date.now() }; let result = await postData("todos", todoData); console.log("Todo hinzugefügt:", result.name); return result; } // Alle Todos laden async function loadTodos() { let todos = await loadDataFromPath("todos"); displayTodos(todos); } // Todo als erledigt markieren async function completeTodo(todoId) { await patchData(`todos/${todoId}`, { completed: true }); loadTodos(); // Neu laden } // Todo löschen async function deleteTodo(todoId) { await deleteData(`todos/${todoId}`); loadTodos(); // Neu laden } function displayTodos(todos) { let html = '<h2>Meine Todos:</h2><ul>'; for (let id in todos) { let todo = todos[id]; let status = todo.completed ? '✅' : '❌'; html += ` <li> ${status} ${todo.text} <button onclick="completeTodo('${id}')">Erledigt</button> <button onclick="deleteTodo('${id}')">Löschen</button> </li> `; } html += '</ul>'; document.getElementById('todoList').innerHTML = html; } ``` ### Beispiel 3: User Management mit Firebase ```javascript // Benutzer registrieren async function registerUser(userData) { try { let result = await postData("users", { name: userData.name, email: userData.email, registered: Date.now(), active: true }); console.log("User registriert:", result.name); return result.name; // Firebase generierte ID } catch (error) { console.error("Registrierung fehlgeschlagen:", error); } } // Benutzer-Profil laden async function getUserProfile(userId) { try { let profile = await loadDataFromPath(`users/${userId}`); return profile; } catch (error) { console.error("Profil nicht gefunden:", error); } } // Benutzer-Profil aktualisieren async function updateUserProfile(userId, updates) { try { await patchData(`users/${userId}`, updates); console.log("Profil aktualisiert"); } catch (error) { console.error("Update fehlgeschlagen:", error); } } // Alle aktiven Benutzer laden async function getActiveUsers() { try { let allUsers = await loadDataFromPath("users"); let activeUsers = {}; for (let id in allUsers) { if (allUsers[id].active) { activeUsers[id] = allUsers[id]; } } return activeUsers; } catch (error) { console.error("Fehler beim Laden aktiver User:", error); } } ``` ### Beispiel 4: Mehrere API-Calls kombinieren ```javascript async function compareFruits(fruit1, fruit2) { try { let [data1, data2] = await Promise.all([ fetch(`https://www.fruityvice.com/api/fruit/${fruit1}`).then(r => r.json()), fetch(`https://www.fruityvice.com/api/fruit/${fruit2}`).then(r => r.json()) ]); console.log(`${fruit1}: ${data1.nutritions.calories} Kalorien`); console.log(`${fruit2}: ${data2.nutritions.calories} Kalorien`); let winner = data1.nutritions.calories < data2.nutritions.calories ? fruit1 : fruit2; console.log(`${winner} hat weniger Kalorien!`); } catch (error) { console.error('Fehler beim Vergleich:', error); } } // Verwendung compareFruits('apple', 'banana'); ``` --- ## 🚀 Erweiterte Techniken ### Promise.all() für parallele Requests ```javascript async function loadMultipleFruits() { let fruitNames = ['apple', 'banana', 'orange']; try { let promises = fruitNames.map(name => fetch(`https://www.fruityvice.com/api/fruit/${name}`) .then(response => response.json()) ); let results = await Promise.all(promises); console.log('Alle Früchte geladen:', results); } catch (error) { console.error('Fehler bei einem Request:', error); } } ``` ### Timeout für Requests ```javascript function fetchWithTimeout(url, timeout = 5000) { return Promise.race([ fetch(url), new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), timeout) ) ]); } async function getFruitWithTimeout(fruitName) { try { let response = await fetchWithTimeout( `https://www.fruityvice.com/api/fruit/${fruitName}`, 3000 ); let fruit = await response.json(); return fruit; } catch (error) { console.error('Request zu langsam oder fehlgeschlagen:', error); } } ``` ### Firebase Realtime Updates (Advanced) ```javascript // Server-Sent Events für Realtime Updates function listenToFirebaseChanges(path) { const eventSource = new EventSource( `${BASE_URL}${path}.json?sse=true` ); eventSource.onmessage = function(event) { const data = JSON.parse(event.data); console.log('Daten geändert:', data); // UI aktualisieren updateUI(data); }; eventSource.onerror = function(error) { console.error('SSE Fehler:', error); }; return eventSource; } // Verwendung const listener = listenToFirebaseChanges('todos'); // listener.close(); // Verbindung schließen ``` --- ## 🔧 Debugging-Tipps ### Console.log richtig nutzen ```javascript async function debugFetch() { console.log('1. Starte Request...'); try { let response = await fetch('https://www.fruityvice.com/api/fruit/apple'); console.log('2. Response erhalten:', response.status); let data = await response.json(); console.log('3. Daten geparst:', data); } catch (error) { console.error('4. Fehler aufgetreten:', error); } } ``` ### Firebase Debugging ```javascript // Firebase Response Status prüfen async function debugFirebaseRequest() { try { let response = await fetch(BASE_URL + ".json"); console.log("Status:", response.status); console.log("OK:", response.ok); console.log("Headers:", response.headers); if (response.status === 401) { console.error("Firebase Berechtigung fehlt!"); } let data = await response.json(); console.log("Data:", data); } catch (error) { console.error("Firebase Fehler:", error); } } ``` ### Häufige Fehlerquellen - **CORS-Probleme** bei externen APIs - **Falsche URL** oder Endpoint - **Firebase Sicherheitsregeln** zu restriktiv - **Vergessenes await** bei async Funktionen - **response.json()** statt **response.text()** bei Text-Dateien - **Falsche HTTP-Methoden** (GET, POST, PUT, DELETE) --- ## 📊 Performance & Best Practices ### Caching implementieren ```javascript // Einfacher Memory Cache const cache = new Map(); async function getCachedData(url, cacheTime = 5 * 60 * 1000) { // 5 Minuten const now = Date.now(); const cached = cache.get(url); if (cached && (now - cached.timestamp) < cacheTime) { console.log('Aus Cache geladen'); return cached.data; } console.log('Neue Anfrage'); const response = await fetch(url); const data = await response.json(); cache.set(url, { data: data, timestamp: now }); return data; } ``` ### Rate Limiting ```javascript // Request Queue für Rate Limiting class RequestQueue { constructor(maxConcurrent = 3, delay = 1000) { this.queue = []; this.running = 0; this.maxConcurrent = maxConcurrent; this.delay = delay; } async add(fetchPromise) { return new Promise((resolve, reject) => { this.queue.push({ fetchPromise, resolve, reject }); this.process(); }); } async process() { if (this.running >= this.maxConcurrent || this.queue.length === 0) { return; } this.running++; const { fetchPromise, resolve, reject } = this.queue.shift(); try { const result = await fetchPromise(); resolve(result); } catch (error) { reject(error); } this.running--; setTimeout(() => { this.process(); }, this.delay); } } // Verwendung const requestQueue = new RequestQueue(2, 500); // Max 2 parallel, 500ms delay async function queuedRequest(url) { return requestQueue.add(() => fetch(url).then(r => r.json())); } ``` --- ## 📝 Zusammenfassung ### Key Takeaways - **Promises** vereinfachen asynchrone Programmierung - **Async/Await** macht Code lesbarer als .then() - **Try/Catch** für saubere Fehlerbehandlung - **Fetch API** für moderne HTTP-Requests - **Firebase** bietet einfache Cloud-Database - **Externe APIs** erweitern App-Funktionalität ### Firebase vs. Externe APIs | Aspekt | Firebase | Externe APIs | |--------|----------|--------------| | **Setup** | Einfach | Variiert | | **Kosten** | Kostenlos (Limits) | Oft kostenpflichtig | | **Kontrolle** | Eigene Daten | Fremd-kontrolliert | | **Offline** | Ja | Nein | | **Realtime** | Ja | Selten | ### Best Practices - **Immer Fehlerbehandlung** implementieren - **Await nicht vergessen** bei async Funktionen - **Loading States** für bessere UX - **API-Limits** und **Rate Limiting** beachten - **Caching** für Performance - **Firebase Sicherheitsregeln** für Production ### Nächste Schritte - **Firebase Authentication** implementieren - **Progressive Web Apps (PWA)** entwickeln - **Service Workers** für Offline-Funktionalität - **WebSockets** für Realtime-Features - **GraphQL** als Alternative zu REST --- ## 🔗 Nützliche Links ### Dokumentation - [MDN Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) - [Firebase Dokumentation](https://firebase.google.com/docs) - [Promises MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) ### APIs zum Üben - [JSONPlaceholder](https://jsonplaceholder.typicode.com/) - Fake REST API - [Fruityvice](https://www.fruityvice.com/) - Frucht-Daten - [Cat Facts API](https://catfact.ninja/) - Katzen-Fakten - [Random User API](https://randomuser.me/) - Zufällige User-Daten ### Tools - [Postman](https://www.postman.com/) - API Testing - [Firebase Console](https://console.firebase.google.com/) - Firebase Management - [Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools) - Browser Debugging