Vai al contenuto

Promise e async/await

Gestisci operazioni asincrone con Promise, async/await e fetch API.

Programmazione Asincrona

JavaScript Γ¨ single-threaded (un solo thread), ma puΓ² gestire operazioni asincrone come chiamate API, timer, lettura file senza bloccare l'esecuzione. Le Promise e async/await rendono questo processo pulito e gestibile!

Cos'Γ¨ il Codice Asincrono?

Immagina di ordinare una pizza online:

  • πŸ• Sincrono: Aspetti davanti al forno finchΓ© Γ¨ pronta (blocchi tutto)
  • ⏰ Asincrono: Ordini e fai altro, ricevi notifica quando pronta (non blocchi)

JavaScript usa l'approccio asincrono per operazioni lunghe come fetch API, timer, ecc.

🎯 In Questa Lezione

  • βœ… Promise - Gestire operazioni asincrone
  • βœ… then/catch/finally - Gestire successo/errore
  • βœ… async/await - Sintassi moderna (⭐ CONSIGLIATA)
  • βœ… fetch API - Chiamate HTTP
  • βœ… Error Handling - Gestire errori
  • βœ… Promise.all/race - Promise multiple

Il Problema: Callback Hell

Prima delle Promise, JavaScript usava solo callback per operazioni asincrone. Questo portava al temuto "Callback Hell" o "Pyramid of Doom"!

Callback Hell (❌ Da Evitare)

// ❌ CALLBACK HELL - Difficile da leggere e mantenere
getUser(userId, (user) => {
    getOrders(user.id, (orders) => {
        getOrderDetails(orders[0].id, (details) => {
            getPaymentInfo(details.paymentId, (payment) => {
                processPayment(payment, (result) => {
                    console.log("Pagamento completato!");
                }, (error) => {
                    console.error("Errore pagamento:", error);
                });
            }, (error) => {
                console.error("Errore payment info:", error);
            });
        }, (error) => {
            console.error("Errore dettagli:", error);
        });
    }, (error) => {
        console.error("Errore ordini:", error);
    });
}, (error) => {
    console.error("Errore utente:", error);
});

// Problemi:
// 😱 Difficile da leggere (piramide)
// 😱 Gestione errori ripetitiva
// 😱 Difficile da debuggare
// 😱 Difficile aggiungere logica

πŸ’‘ La Soluzione: Promise e Async/Await

Le Promise e async/await risolvono questi problemi rendendo il codice asincrono piΓΉ leggibile, gestibile e simile a codice sincrono!

Promise

Una Promise Γ¨ un oggetto che rappresenta il completamento (o fallimento) futuro di un'operazione asincrona.

  1. Stati di una Promise

Una Promise puΓ² essere in uno di tre stati:

Stati Promise

// 1. PENDING (in attesa) - Stato iniziale
// 2. FULFILLED (risolta) - Operazione completata con successo
// 3. REJECTED (rifiutata) - Operazione fallita con errore

// ⚠️ Una volta risolta/rifiutata, lo stato è IMMUTABILE
  1. Creare una Promise

Promise Constructor

// Sintassi: new Promise((resolve, reject) => { ... })

const myPromise = new Promise((resolve, reject) => {
    // Operazione asincrona
    const success = true;

    if (success) {
        resolve("Operazione riuscita!");  // Promise FULFILLED
    } else {
        reject("Operazione fallita!");    // Promise REJECTED
    }
});

// Esempio: Simula chiamata API
function fetchUserData(userId) {
    return new Promise((resolve, reject) => {
        // Simula delay rete
        setTimeout(() => {
            if (userId > 0) {
                const user = {
                    id: userId,
                    nome: "Mario",
                    email: "mario@example.com"
                };
                resolve(user);  // Successo
            } else {
                reject(new Error("User ID non valido"));  // Errore
            }
        }, 1000);  // 1 secondo delay
    });
}

// Esempio: Promise immediata
const promiseRisolta = Promise.resolve("Valore");
const promiseRifiutata = Promise.reject("Errore");
  1. Consumare Promise: then/catch/finally

then/catch/finally

fetchUserData(1)
    .then((user) => {
        // Eseguito se Promise FULFILLED
        console.log("Utente:", user);
        return user.id;  // Passa al prossimo then
    })
    .then((userId) => {
        // Chain - prende return precedente
        console.log("User ID:", userId);
    })
    .catch((error) => {
        // Eseguito se Promise REJECTED
        console.error("Errore:", error);
    })
    .finally(() => {
        // Eseguito SEMPRE (successo o errore)
        console.log("Operazione completata");
    });

// Output (dopo 1 secondo):
// Utente: {id: 1, nome: "Mario", email: "mario@example.com"}
// User ID: 1
// Operazione completata

// Con errore
fetchUserData(-1)
    .then((user) => {
        console.log("Utente:", user);
    })
    .catch((error) => {
        console.error("Errore:", error.message);
    })
    .finally(() => {
        console.log("Fine");
    });

// Output (dopo 1 secondo):
// Errore: User ID non valido
// Fine
  1. Promise Chaining

Concatena piΓΉ operazioni asincrone in sequenza.

Promise Chain

// Ogni then ritorna una nuova Promise
function getUser(id) {
    return new Promise((resolve) => {
        setTimeout(() => resolve({ id, nome: "Mario" }), 500);
    });
}

function getOrders(userId) {
    return new Promise((resolve) => {
        setTimeout(() => resolve([
            { id: 1, total: 100 },
            { id: 2, total: 200 }
        ]), 500);
    });
}

function getOrderDetails(orderId) {
    return new Promise((resolve) => {
        setTimeout(() => resolve({
            id: orderId,
            items: ["Item 1", "Item 2"],
            total: 100
        }), 500);
    });
}

// βœ… Con Promise Chaining (meglio di callback hell!)
getUser(1)
    .then((user) => {
        console.log("User:", user.nome);
        return getOrders(user.id);  // Ritorna Promise
    })
    .then((orders) => {
        console.log("Orders:", orders.length);
        return getOrderDetails(orders[0].id);  // Ritorna Promise
    })
    .then((details) => {
        console.log("Dettagli:", details);
    })
    .catch((error) => {
        console.error("Errore:", error);
    });

// Output (dopo ~1.5 secondi):
// User: Mario
// Orders: 2
// Dettagli: {id: 1, items: [...], total: 100}

// Molto piΓΉ leggibile del callback hell! βœ…

Async/Await (ES2017)

async/await è syntactic sugar per Promise che rende il codice asincrono sembrare sincrono. 🌟 QUESTO È IL METODO MODERNO CONSIGLIATO!

  1. Sintassi async/await

async/await Base

// async - Dichiara funzione asincrona (ritorna sempre Promise)
async function myFunction() {
    return "Hello";  // Automaticamente wrapped in Promise
}

myFunction().then(result => console.log(result));  // "Hello"

// await - Aspetta risoluzione Promise (SOLO dentro async function!)
async function fetchData() {
    const promise = new Promise((resolve) => {
        setTimeout(() => resolve("Dati caricati"), 1000);
    });

    const result = await promise;  // Aspetta 1 secondo
    console.log(result);
}

fetchData();  // Output dopo 1 secondo: "Dati caricati"

// ⚠️ await SOLO in async function!
// await somePromise;  // ERRORE se fuori async function
  1. Confronto then vs async/await

Confronto

// ❌ Con then (vecchio modo)
function fetchUserThen(id) {
    return getUser(id)
        .then((user) => {
            console.log("User:", user.nome);
            return getOrders(user.id);
        })
        .then((orders) => {
            console.log("Orders:", orders.length);
            return getOrderDetails(orders[0].id);
        })
        .then((details) => {
            console.log("Details:", details);
            return details;
        })
        .catch((error) => {
            console.error("Errore:", error);
        });
}

// βœ… Con async/await (MODERNO - CONSIGLIATO! 🌟)
async function fetchUserAsync(id) {
    try {
        const user = await getUser(id);
        console.log("User:", user.nome);

        const orders = await getOrders(user.id);
        console.log("Orders:", orders.length);

        const details = await getOrderDetails(orders[0].id);
        console.log("Details:", details);

        return details;
    } catch (error) {
        console.error("Errore:", error);
    }
}

// Molto piΓΉ leggibile! Sembra codice sincrono! βœ…
  1. Error Handling con try/catch

try/catch

async function fetchData(url) {
    try {
        const response = await fetch(url);

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

        const data = await response.json();
        return data;

    } catch (error) {
        console.error("Errore nel fetch:", error.message);
        // Gestione errore
        return null;
    } finally {
        console.log("Fetch completato");
    }
}

// Uso
const data = await fetchData("https://api.example.com/users");
if (data) {
    console.log("Dati:", data);
} else {
    console.log("Nessun dato");
}

// Esempio: Validazione e retry
async function fetchWithRetry(url, retries = 3) {
    for (let i = 0; i < retries; i++) {
        try {
            const response = await fetch(url);

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

            console.log(`Tentativo ${i + 1} fallito`);

        } catch (error) {
            if (i === retries - 1) throw error;  // Ultimo tentativo
            console.log(`Errore, riprovo... (${i + 1}/${retries})`);
        }
    }
}

// Uso
try {
    const data = await fetchWithRetry("https://api.example.com/data");
    console.log(data);
} catch (error) {
    console.error("Tutti i tentativi falliti:", error);
}

Fetch API

fetch() Γ¨ l'API moderna per fare chiamate HTTP. Ritorna una Promise!

  1. GET Request

fetch() GET

// Sintassi base
async function getUsers() {
    try {
        const response = await fetch("https://jsonplaceholder.typicode.com/users");

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

        // Parse JSON
        const users = await response.json();

        console.log("Utenti:", users);
        return users;

    } catch (error) {
        console.error("Errore fetch:", error);
    }
}

// Uso
const users = await getUsers();

// Con headers
async function getUsersWithHeaders() {
    const response = await fetch("https://api.example.com/users", {
        headers: {
            "Content-Type": "application/json",
            "Authorization": "Bearer token123"
        }
    });

    return await response.json();
}
  1. POST/PUT/DELETE Requests

fetch() POST/PUT/DELETE

// POST - Crea risorsa
async function createUser(userData) {
    try {
        const response = await fetch("https://api.example.com/users", {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify(userData)
        });

        const newUser = await response.json();
        return newUser;

    } catch (error) {
        console.error("Errore creazione:", error);
    }
}

// Uso
const newUser = await createUser({
    nome: "Mario",
    email: "mario@example.com"
});

// PUT - Aggiorna risorsa
async function updateUser(id, updates) {
    const response = await fetch(`https://api.example.com/users/${id}`, {
        method: "PUT",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify(updates)
    });

    return await response.json();
}

// Uso
await updateUser(1, { nome: "Mario Updated" });

// DELETE - Elimina risorsa
async function deleteUser(id) {
    const response = await fetch(`https://api.example.com/users/${id}`, {
        method: "DELETE"
    });

    if (response.ok) {
        console.log("Utente eliminato");
    }
}

// Uso
await deleteUser(1);
  1. Gestione Response Completa

Response Handling

async function fetchWithFullHandling(url) {
    try {
        const response = await fetch(url);

        // Status code
        console.log("Status:", response.status);
        console.log("OK:", response.ok);  // true se 200-299

        // Headers
        console.log("Content-Type:", response.headers.get("Content-Type"));

        // Parse in base a Content-Type
        const contentType = response.headers.get("Content-Type");

        let data;
        if (contentType?.includes("application/json")) {
            data = await response.json();
        } else if (contentType?.includes("text/")) {
            data = await response.text();
        } else {
            data = await response.blob();  // File binario
        }

        return { status: response.status, data };

    } catch (error) {
        // Network error o parsing error
        console.error("Errore:", error);
        throw error;
    }
}

// Esempio completo con loading e error states
async function fetchUserProfile(userId) {
    const state = {
        loading: true,
        data: null,
        error: null
    };

    try {
        const response = await fetch(`/api/users/${userId}`);

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

        state.data = await response.json();

    } catch (error) {
        state.error = error.message;
    } finally {
        state.loading = false;
    }

    return state;
}

// Uso
const profile = await fetchUserProfile(1);

if (profile.loading) {
    console.log("Caricamento...");
} else if (profile.error) {
    console.error("Errore:", profile.error);
} else {
    console.log("Dati:", profile.data);
}

Promise Multiple

Gestisci piΓΉ Promise contemporaneamente con metodi statici.

  1. Promise.all() - Tutte le Promise

Aspetta che TUTTE le Promise si risolvano (o una fallisca).

Promise.all()

// Esegue in parallelo, aspetta tutte
const promise1 = fetch("/api/users");
const promise2 = fetch("/api/posts");
const promise3 = fetch("/api/comments");

try {
    const [users, posts, comments] = await Promise.all([
        promise1.then(r => r.json()),
        promise2.then(r => r.json()),
        promise3.then(r => r.json())
    ]);

    console.log("Users:", users);
    console.log("Posts:", posts);
    console.log("Comments:", comments);

} catch (error) {
    // Se UNA sola fallisce, tutto fallisce
    console.error("Errore:", error);
}

// Esempio pratico: Dashboard data
async function loadDashboard() {
    const startTime = Date.now();

    try {
        const [userData, salesData, analyticsData] = await Promise.all([
            fetchUserData(),
            fetchSalesData(),
            fetchAnalytics()
        ]);

        const loadTime = Date.now() - startTime;
        console.log(`Dati caricati in ${loadTime}ms`);

        return { userData, salesData, analyticsData };

    } catch (error) {
        console.error("Errore caricamento dashboard:", error);
    }
}

// Molto piΓΉ veloce che in sequenza! βœ…
  1. Promise.race() - Prima che Finisce

Ritorna la prima Promise che si risolve (o fallisce).

Promise.race()

// Prima che finisce vince
const slow = new Promise(resolve =>
    setTimeout(() => resolve("Lento"), 3000)
);

const fast = new Promise(resolve =>
    setTimeout(() => resolve("Veloce"), 1000)
);

const result = await Promise.race([slow, fast]);
console.log(result);  // "Veloce" (dopo 1 secondo)

// Caso d'uso: Timeout
async function fetchWithTimeout(url, timeout = 5000) {
    const fetchPromise = fetch(url);

    const timeoutPromise = new Promise((_, reject) =>
        setTimeout(() => reject(new Error("Timeout!")), timeout)
    );

    try {
        const response = await Promise.race([fetchPromise, timeoutPromise]);
        return await response.json();
    } catch (error) {
        if (error.message === "Timeout!") {
            console.error("Request troppo lenta!");
        }
        throw error;
    }
}

// Uso
try {
    const data = await fetchWithTimeout("/api/slow-endpoint", 3000);
    console.log(data);
} catch (error) {
    console.error("Errore:", error.message);
}
  1. Promise.allSettled() - Tutte Completate

Aspetta tutte, anche se alcune falliscono.

Promise.allSettled()

// Non fallisce mai - aspetta tutte
const promises = [
    fetch("/api/users").then(r => r.json()),
    fetch("/api/posts").then(r => r.json()),
    fetch("/api/invalid").then(r => r.json())  // Fallisce
];

const results = await Promise.allSettled(promises);

results.forEach((result, index) => {
    if (result.status === "fulfilled") {
        console.log(`Promise ${index}: Success`, result.value);
    } else {
        console.log(`Promise ${index}: Failed`, result.reason);
    }
});

// Output:
// Promise 0: Success [users data]
// Promise 1: Success [posts data]
// Promise 2: Failed TypeError: Failed to fetch

// Caso d'uso: Batch operations
async function deleteMultipleUsers(userIds) {
    const deletePromises = userIds.map(id =>
        fetch(`/api/users/${id}`, { method: "DELETE" })
    );

    const results = await Promise.allSettled(deletePromises);

    const succeeded = results.filter(r => r.status === "fulfilled").length;
    const failed = results.filter(r => r.status === "rejected").length;

    console.log(`Eliminati: ${succeeded}, Falliti: ${failed}`);

    return { succeeded, failed, results };
}
  1. Promise.any() - Prima che Riesce

Ritorna la prima Promise che si risolve con successo.

Promise.any()

// Prima che RISOLVE vince (ignora errori)
const promises = [
    fetch("/api/primary").then(r => r.json()),
    fetch("/api/fallback1").then(r => r.json()),
    fetch("/api/fallback2").then(r => r.json())
];

try {
    const data = await Promise.any(promises);
    console.log("Prima disponibile:", data);
} catch (error) {
    // Fallisce solo se TUTTE falliscono
    console.error("Tutte le API sono down!");
}

// Caso d'uso: API redundancy
async function fetchFromMultipleSources(endpoint) {
    const sources = [
        `https://api1.example.com${endpoint}`,
        `https://api2.example.com${endpoint}`,
        `https://api3.example.com${endpoint}`
    ];

    const promises = sources.map(url =>
        fetch(url).then(r => r.json())
    );

    // Usa la prima API che risponde
    return await Promise.any(promises);
}

Esempio Completo: API Client

API Client Professionale

// === API CLIENT CLASS ===
class APIClient {
    constructor(baseURL, options = {}) {
        this.baseURL = baseURL;
        this.timeout = options.timeout || 10000;
        this.retries = options.retries || 3;
        this.headers = options.headers || {};
    }

    // Request con timeout
    async fetchWithTimeout(url, options, timeout) {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), timeout);

        try {
            const response = await fetch(url, {
                ...options,
                signal: controller.signal
            });
            clearTimeout(timeoutId);
            return response;
        } catch (error) {
            clearTimeout(timeoutId);
            if (error.name === 'AbortError') {
                throw new Error('Request timeout');
            }
            throw error;
        }
    }

    // Request con retry
    async request(endpoint, options = {}) {
        const url = `${this.baseURL}${endpoint}`;

        const config = {
            headers: {
                'Content-Type': 'application/json',
                ...this.headers,
                ...options.headers
            },
            ...options
        };

        let lastError;

        for (let attempt = 0; attempt < this.retries; attempt++) {
            try {
                console.log(`Tentativo ${attempt + 1}/${this.retries}`);

                const response = await this.fetchWithTimeout(
                    url,
                    config,
                    this.timeout
                );

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

                const data = await response.json();
                return { success: true, data };

            } catch (error) {
                lastError = error;
                console.error(`Tentativo ${attempt + 1} fallito:`, error.message);

                if (attempt < this.retries - 1) {
                    // Exponential backoff
                    const delay = Math.pow(2, attempt) * 1000;
                    console.log(`Riprovo tra ${delay}ms...`);
                    await new Promise(resolve => setTimeout(resolve, delay));
                }
            }
        }

        return {
            success: false,
            error: lastError.message
        };
    }

    // GET
    async get(endpoint) {
        return await this.request(endpoint, { method: 'GET' });
    }

    // POST
    async post(endpoint, data) {
        return await this.request(endpoint, {
            method: 'POST',
            body: JSON.stringify(data)
        });
    }

    // PUT
    async put(endpoint, data) {
        return await this.request(endpoint, {
            method: 'PUT',
            body: JSON.stringify(data)
        });
    }

    // DELETE
    async delete(endpoint) {
        return await this.request(endpoint, { method: 'DELETE' });
    }

    // Batch requests
    async batchGet(endpoints) {
        const promises = endpoints.map(endpoint => this.get(endpoint));
        return await Promise.allSettled(promises);
    }
}

// === USO ===

// Inizializza client
const api = new APIClient('https://jsonplaceholder.typicode.com', {
    timeout: 5000,
    retries: 3,
    headers: {
        'Authorization': 'Bearer token123'
    }
});

// === ESEMPIO 1: GET singolo ===
async function getUser(id) {
    console.log("=== GET USER ===");
    const result = await api.get(`/users/${id}`);

    if (result.success) {
        console.log("Utente:", result.data);
    } else {
        console.error("Errore:", result.error);
    }
}

await getUser(1);

// === ESEMPIO 2: POST ===
async function createPost() {
    console.log("\n=== CREATE POST ===");
    const newPost = {
        title: "Nuovo Post",
        body: "Contenuto del post",
        userId: 1
    };

    const result = await api.post('/posts', newPost);

    if (result.success) {
        console.log("Post creato:", result.data);
    } else {
        console.error("Errore:", result.error);
    }
}

await createPost();

// === ESEMPIO 3: Batch requests ===
async function loadDashboard() {
    console.log("\n=== LOAD DASHBOARD ===");
    const startTime = Date.now();

    const endpoints = ['/users/1', '/posts', '/comments'];
    const results = await api.batchGet(endpoints);

    const loadTime = Date.now() - startTime;
    console.log(`Caricamento completato in ${loadTime}ms`);

    results.forEach((result, index) => {
        if (result.status === 'fulfilled' && result.value.success) {
            console.log(`βœ… Endpoint ${index + 1}: Success`);
        } else {
            console.log(`❌ Endpoint ${index + 1}: Failed`);
        }
    });

    return results;
}

await loadDashboard();

// === ESEMPIO 4: Error handling completo ===
async function complexOperation() {
    console.log("\n=== COMPLEX OPERATION ===");

    try {
        // Step 1: Get user
        console.log("Step 1: Carico utente...");
        const userResult = await api.get('/users/1');

        if (!userResult.success) {
            throw new Error("Impossibile caricare utente");
        }

        const user = userResult.data;
        console.log("Utente:", user.name);

        // Step 2: Get user's posts
        console.log("Step 2: Carico post...");
        const postsResult = await api.get(`/users/${user.id}/posts`);

        if (!postsResult.success) {
            throw new Error("Impossibile caricare post");
        }

        const posts = postsResult.data;
        console.log(`Post trovati: ${posts.length}`);

        // Step 3: Get comments for first post
        if (posts.length > 0) {
            console.log("Step 3: Carico commenti...");
            const commentsResult = await api.get(`/posts/${posts[0].id}/comments`);

            if (commentsResult.success) {
                const comments = commentsResult.data;
                console.log(`Commenti trovati: ${comments.length}`);
            }
        }

        console.log("βœ… Operazione completata con successo!");

    } catch (error) {
        console.error("❌ Operazione fallita:", error.message);
    } finally {
        console.log("Pulizia risorse...");
    }
}

await complexOperation();

// Output:
// === GET USER ===
// Tentativo 1/3
// Utente: {id: 1, name: "Leanne Graham", ...}
//
// === CREATE POST ===
// Tentativo 1/3
// Post creato: {id: 101, title: "Nuovo Post", ...}
//
// === LOAD DASHBOARD ===
// Tentativo 1/3
// Tentativo 1/3
// Tentativo 1/3
// Caricamento completato in 245ms
// βœ… Endpoint 1: Success
// βœ… Endpoint 2: Success
// βœ… Endpoint 3: Success
//
// === COMPLEX OPERATION ===
// Step 1: Carico utente...
// Tentativo 1/3
// Utente: Leanne Graham
// Step 2: Carico post...
// Tentativo 1/3
// Post trovati: 10
// Step 3: Carico commenti...
// Tentativo 1/3
// Commenti trovati: 5
// βœ… Operazione completata con successo!
// Pulizia risorse...

πŸ’‘ Features Implementate

  • Timeout - AbortController per limitare tempo
  • Retry logic - Exponential backoff
  • Error handling - try/catch completo
  • Batch requests - Promise.allSettled
  • REST methods - GET, POST, PUT, DELETE
  • Headers config - Authorization, Content-Type
  • Response parsing - JSON automatico
  • Status checking - response.ok

Best Practices

🌟 Regole d'Oro Async/Await

  1. Usa async/await invece di then
// βœ… MODERNO - async/await
async function getData() {
    try {
        const response = await fetch(url);
        const data = await response.json();
        return data;
    } catch (error) {
        console.error(error);
    }
}

// ❌ EVITA - then (meno leggibile)
function getData() {
    return fetch(url)
        .then(r => r.json())
        .then(data => data)
        .catch(error => console.error(error));
}
  1. Sempre try/catch
// βœ… Con error handling
async function safeFetch() {
    try {
        const data = await fetch(url);
        return data;
    } catch (error) {
        console.error("Errore:", error);
        return null;
    }
}

// ❌ Senza error handling (pericoloso!)
async function unsafeFetch() {
    const data = await fetch(url);  // PuΓ² crashare!
    return data;
}
  1. Promise.all per Parallelismo
// βœ… PARALLELO (veloce)
const [users, posts, comments] = await Promise.all([
    fetchUsers(),
    fetchPosts(),
    fetchComments()
]);
// Tempo: ~1 secondo (simultaneo)

// ❌ SEQUENZIALE (lento)
const users = await fetchUsers();     // 1 secondo
const posts = await fetchPosts();     // 1 secondo
const comments = await fetchComments(); // 1 secondo
// Tempo totale: 3 secondi!
  1. Verifica response.ok
// βœ… Controlla status
const response = await fetch(url);

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

const data = await response.json();

// ❌ Senza controllo
const response = await fetch(url);
const data = await response.json();  // Fallisce su 404!

🎯 Riepilogo Lezione

Concetto Quando Usare Sintassi
async/await ⭐ SEMPRE (moderno) await promise
try/catch Gestire errori try { await } catch(e) {}
fetch() Chiamate HTTP await fetch(url)
Promise.all() PiΓΉ Promise parallele await Promise.all([...])
Promise.race() Prima che finisce await Promise.race([...])
Promise.allSettled() Tutte (anche errori) await Promise.allSettled([...])

CONGRATULAZIONI! πŸŽ‰

Hai completato tutte le 16 lezioni del corso JavaScript!

Ora conosci:

βœ… Variabili e Tipi Dati βœ… Operatori ed Espressioni βœ… Stringhe e Array

βœ… Oggetti e Metodi βœ… Condizionali e Cicli βœ… Funzioni Base e Avanzate

βœ… Array Methods (map/filter/reduce) βœ… DOM Manipulation βœ… Eventi

βœ… Destructuring & Spread βœ… Promise & Async/Await βœ… Fetch API

πŸš€ Sei pronto per costruire applicazioni JavaScript moderne!

πŸŽ‰ CORSO COMPLETATO!

Percorso principale completato!

Hai finito il percorso principale. Le lezioni che seguono sono approfondimenti con altri esempi e un progetto To-Do completo: usale per ripassare.

Continua a praticare e costruisci progetti reali per consolidare le tue competenze! πŸ’ͺ