Async/Await in JavaScript meistern
Async/Await in JavaScript
async/await ist die moderne Art, asynchronen Code zu schreiben. Es macht Promise-basierten Code lesbar wie synchronen Code – keine Callback-Hölle, keine verschachtelten .then()-Ketten.
Warum async/await?
Vergleiche diese drei Ansätze:
Callbacks (Alt)
// Callback-Hölle 😱
getUser(userId, (user) => {
getOrders(user.id, (orders) => {
getOrderDetails(orders[0].id, (details) => {
console.log(details);
});
});
});
Promises (Besser)
// Promise-Kette
getUser(userId)
.then(user => getOrders(user.id))
.then(orders => getOrderDetails(orders[0].id))
.then(details => console.log(details))
.catch(error => console.error(error));
Async/Await (Modern!)
// Klar und lesbar ✨
async function loadOrderDetails(userId) {
const user = await getUser(userId);
const orders = await getOrders(user.id);
const details = await getOrderDetails(orders[0].id);
console.log(details);
}
Die Grundlagen
async Funktionen
Das async-Keyword macht eine Funktion asynchron:
// Normale Funktion
function normal() {
return "Hallo";
}
// Async Funktion - gibt immer ein Promise zurück!
async function asyncVersion() {
return "Hallo";
}
// Beide aufrufen
console.log(normal()); // "Hallo"
console.log(asyncVersion()); // Promise { "Hallo" }
// Async-Ergebnis nutzen
asyncVersion().then(result => console.log(result)); // "Hallo"
await verwenden
await wartet auf ein Promise und gibt dessen Wert zurück:
async function example() {
// Promise erstellen
const promise = new Promise(resolve => {
setTimeout(() => resolve("Fertig!"), 1000);
});
console.log("Warte...");
const result = await promise; // Wartet 1 Sekunde
console.log(result); // "Fertig!"
}
example();
// Ausgabe:
// "Warte..."
// (1 Sekunde Pause)
// "Fertig!"
Wichtig: await funktioniert nur innerhalb von async-Funktionen!
// ❌ Fehler!
function notAsync() {
const result = await somePromise; // SyntaxError!
}
// ✅ Korrekt
async function isAsync() {
const result = await somePromise;
}
// ✅ Top-Level await (in Modulen)
const data = await fetch('/api/data');
Fehlerbehandlung mit try/catch
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error("Fehler beim Laden:", error.message);
return null;
}
}
// Verwendung
const user = await fetchUser(123);
if (user) {
console.log(`Willkommen, ${user.name}!`);
} else {
console.log("User konnte nicht geladen werden");
}
Mehrere try/catch Blöcke
async function processOrder(orderId) {
let order;
let payment;
// Order laden
try {
order = await fetchOrder(orderId);
} catch (error) {
console.error("Order nicht gefunden");
return { success: false, step: "order" };
}
// Zahlung verarbeiten
try {
payment = await processPayment(order);
} catch (error) {
console.error("Zahlung fehlgeschlagen");
return { success: false, step: "payment" };
}
return { success: true, order, payment };
}
Parallele Ausführung
Sequentiell (langsam)
async function loadSequential() {
console.time("sequential");
const users = await fetchUsers(); // 1 Sekunde
const products = await fetchProducts(); // 1 Sekunde
const orders = await fetchOrders(); // 1 Sekunde
console.timeEnd("sequential");
// sequential: ~3000ms 😴
}
Parallel mit Promise.all (schnell!)
async function loadParallel() {
console.time("parallel");
const [users, products, orders] = await Promise.all([
fetchUsers(),
fetchProducts(),
fetchOrders()
]);
console.timeEnd("parallel");
// parallel: ~1000ms 🚀
}
Promise.all Fehlerbehandlung
async function loadAllOrNothing() {
try {
// Wenn EINER fehlschlägt, schlagen ALLE fehl
const [users, products] = await Promise.all([
fetchUsers(),
fetchProducts()
]);
return { users, products };
} catch (error) {
console.error("Mindestens ein Request fehlgeschlagen");
return null;
}
}
Promise.allSettled (Alle Ergebnisse)
async function loadAllResults() {
const results = await Promise.allSettled([
fetchUsers(),
fetchProducts(),
fetchOrders()
]);
results.forEach((result, index) => {
if (result.status === "fulfilled") {
console.log(`Request ${index}: Erfolg`, result.value);
} else {
console.log(`Request ${index}: Fehler`, result.reason);
}
});
// Nur erfolgreiche extrahieren
const successfulData = results
.filter(r => r.status === "fulfilled")
.map(r => r.value);
}
Promise.race (Der Schnellste gewinnt)
async function fetchWithTimeout(url, timeout = 5000) {
const fetchPromise = fetch(url);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("Timeout!")), timeout);
});
// Wer zuerst fertig ist
return Promise.race([fetchPromise, timeoutPromise]);
}
try {
const response = await fetchWithTimeout("/api/data", 3000);
const data = await response.json();
} catch (error) {
console.log(error.message); // "Timeout!" wenn zu langsam
}
Fetch API mit async/await
GET Request
async function getUsers() {
const response = await fetch("https://api.example.com/users");
const users = await response.json();
return users;
}
POST Request
async function createUser(userData) {
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error(`Error: ${response.status}`);
}
return response.json();
}
// Verwenden
const newUser = await createUser({
name: "Max",
email: "max@example.com"
});
Kompletter API-Service
const API_BASE = "https://api.example.com";
const api = {
async get(endpoint) {
const response = await fetch(`${API_BASE}${endpoint}`);
if (!response.ok) throw new Error(`GET failed: ${response.status}`);
return response.json();
},
async post(endpoint, data) {
const response = await fetch(`${API_BASE}${endpoint}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
if (!response.ok) throw new Error(`POST failed: ${response.status}`);
return response.json();
},
async put(endpoint, data) {
const response = await fetch(`${API_BASE}${endpoint}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
if (!response.ok) throw new Error(`PUT failed: ${response.status}`);
return response.json();
},
async delete(endpoint) {
const response = await fetch(`${API_BASE}${endpoint}`, {
method: "DELETE"
});
if (!response.ok) throw new Error(`DELETE failed: ${response.status}`);
return response.ok;
}
};
// Verwendung
const users = await api.get("/users");
const newUser = await api.post("/users", { name: "Max" });
await api.put("/users/1", { name: "Updated" });
await api.delete("/users/1");
Async in Loops
for…of (sequentiell)
async function processSequentially(items) {
for (const item of items) {
await processItem(item); // Wartet auf jeden
}
}
// Beispiel: Dateien nacheinander hochladen
async function uploadFiles(files) {
for (const file of files) {
console.log(`Uploading ${file.name}...`);
await uploadFile(file);
console.log(`${file.name} done!`);
}
}
Promise.all mit map (parallel)
async function processParallel(items) {
await Promise.all(
items.map(item => processItem(item))
);
}
// Beispiel: Alle User parallel laden
async function loadAllUsers(userIds) {
const users = await Promise.all(
userIds.map(id => fetchUser(id))
);
return users;
}
forEach funktioniert NICHT wie erwartet!
// ❌ FALSCH - forEach wartet nicht!
async function wrong(items) {
items.forEach(async (item) => {
await processItem(item);
});
console.log("Fertig"); // Wird sofort ausgegeben!
}
// ✅ RICHTIG - for...of verwenden
async function correct(items) {
for (const item of items) {
await processItem(item);
}
console.log("Fertig"); // Wird nach allen Items ausgegeben
}
Praktisches Beispiel: Wetter-App
const WEATHER_API = "https://api.weatherapi.com/v1";
const API_KEY = "your-api-key";
async function getWeather(city) {
try {
const response = await fetch(
`${WEATHER_API}/current.json?key=${API_KEY}&q=${city}`
);
if (!response.ok) {
if (response.status === 400) {
throw new Error("Stadt nicht gefunden");
}
throw new Error("Wetterdaten konnten nicht geladen werden");
}
const data = await response.json();
return {
city: data.location.name,
country: data.location.country,
temp: data.current.temp_c,
condition: data.current.condition.text,
icon: data.current.condition.icon,
humidity: data.current.humidity,
wind: data.current.wind_kph
};
} catch (error) {
console.error("Fehler:", error.message);
return null;
}
}
async function displayWeather(city) {
const weatherDiv = document.querySelector("#weather");
weatherDiv.textContent = "Laden...";
const weather = await getWeather(city);
if (weather) {
weatherDiv.textContent = `
${weather.city}, ${weather.country}
${weather.temp}°C - ${weather.condition}
Luftfeuchtigkeit: ${weather.humidity}%
Wind: ${weather.wind} km/h
`;
} else {
weatherDiv.textContent = "Wetter konnte nicht geladen werden";
}
}
// Event Handler
document.querySelector("#search-btn").addEventListener("click", async () => {
const city = document.querySelector("#city-input").value;
await displayWeather(city);
});
Async Arrow Functions
// Als Variable
const fetchData = async () => {
const response = await fetch("/api/data");
return response.json();
};
// In Array-Methoden
const userIds = [1, 2, 3];
const users = await Promise.all(
userIds.map(async (id) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
})
);
// Als Event Handler
button.addEventListener("click", async () => {
await saveData();
showNotification("Gespeichert!");
});
// Als IIFE (Immediately Invoked)
(async () => {
const data = await loadInitialData();
initApp(data);
})();
Async Class Methods
class UserService {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
async getUser(id) {
const response = await fetch(`${this.baseUrl}/users/${id}`);
return response.json();
}
async createUser(data) {
const response = await fetch(`${this.baseUrl}/users`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
return response.json();
}
async getUserWithPosts(id) {
const [user, posts] = await Promise.all([
this.getUser(id),
this.getUserPosts(id)
]);
return { ...user, posts };
}
async getUserPosts(userId) {
const response = await fetch(`${this.baseUrl}/users/${userId}/posts`);
return response.json();
}
}
// Verwendung
const userService = new UserService("https://api.example.com");
const userWithPosts = await userService.getUserWithPosts(1);
Best Practices
1. Fehler immer behandeln
// Schlecht
async function bad() {
const data = await fetch("/api"); // Unhandled rejection!
}
// Gut
async function good() {
try {
const data = await fetch("/api");
} catch (error) {
handleError(error);
}
}
2. Parallel wenn möglich
// Unnötig langsam
const a = await fetchA();
const b = await fetchB();
// Schneller
const [a, b] = await Promise.all([fetchA(), fetchB()]);
3. Keine await in Loops wenn parallel möglich
// Langsam
for (const id of ids) {
const user = await fetchUser(id);
}
// Schnell
const users = await Promise.all(ids.map(id => fetchUser(id)));
4. Timeout implementieren
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
signal: controller.signal
});
return response;
} finally {
clearTimeout(timeoutId);
}
}
Zusammenfassung
| Konzept | Beschreibung |
|---|---|
async function | Macht Funktion asynchron |
await | Wartet auf Promise |
try/catch | Fehlerbehandlung |
Promise.all() | Parallel ausführen |
Promise.allSettled() | Alle Ergebnisse (auch Fehler) |
Promise.race() | Der Schnellste gewinnt |
Übung: Baue einen API-Client für eine öffentliche API (z.B. Pokemon API, GitHub API) mit async/await und Promise.all für parallele Requests!