Promise & 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.
- ✅ 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 - 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
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.
Una Promise può essere in uno di tre stati:
// 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
// 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");
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
Concatena più operazioni asincrone in sequenza.
// 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!
// 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
// ❌ 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! ✅
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!
// 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();
}
// 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);
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.
Aspetta che TUTTE le Promise si risolvano (o una fallisca).
// 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! ✅
Ritorna la prima Promise che si risolve (o fallisce).
// 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);
}
Aspetta tutte, anche se alcune falliscono.
// 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 };
}
Ritorna la prima Promise che si risolve con successo.
// 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 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...
- 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
// ✅ 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));
}
// ✅ 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;
}
// ✅ 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!
// ✅ 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!
| 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:
✅ Operatori ed Espressioni
✅ Stringhe e Array
✅ Condizionali e Cicli
✅ Funzioni Base e Avanzate
✅ DOM Manipulation
✅ Eventi
✅ Promise & Async/Await
✅ Fetch API
🚀 Sei pronto per costruire applicazioni JavaScript moderne!
🏆 Corso JavaScript Completato!
Lezione 16 di 16 - 100% Completato
Continua a praticare e costruisci progetti reali per consolidare le tue competenze! 💪