Comprendre l'asynchrone en JavaScript
JavaScript est single-threaded : il ne peut exécuter qu'une seule instruction à la fois. Pourtant, les applications web modernes doivent gérer de nombreuses opérations qui prennent du temps : requêtes API, lecture de fichiers, timers. C'est là qu'intervient la programmation asynchrone avec async/await.
async/await est une syntaxe élégante introduite en ES2017 qui permet d'écrire du code asynchrone de manière séquentielle et lisible, comme du code synchrone. C'est la méthode recommandée en 2026 pour gérer l'asynchrone en JavaScript.
Des callbacks aux Promises, puis à async/await
L'enfer des callbacks (l'ancienne méthode)
// Callback hell - difficile à lire et maintenir
getUser(userId, (user) => {
getOrders(user.id, (orders) => {
getOrderDetails(orders[0].id, (details) => {
getShippingInfo(details.shippingId, (shipping) => {
console.log(shipping); // Enfin !
}, handleError);
}, handleError);
}, handleError);
}, handleError);
Les Promises (amélioration)
// Chaînage de Promises
getUser(userId)
.then(user => getOrders(user.id))
.then(orders => getOrderDetails(orders[0].id))
.then(details => getShippingInfo(details.shippingId))
.then(shipping => console.log(shipping))
.catch(error => console.error(error));
Async/Await (la solution moderne)
// Code clair, linéaire, facile à comprendre
async function getShippingDetails(userId) {
try {
const user = await getUser(userId);
const orders = await getOrders(user.id);
const details = await getOrderDetails(orders[0].id);
const shipping = await getShippingInfo(details.shippingId);
return shipping;
} catch (error) {
console.error('Erreur:', error);
}
}
Syntaxe de base async/await
Déclarer une fonction async
// Fonction classique
async function fetchData() {
const response = await fetch('/api/data');
return response.json();
}
// Fonction fléchée
const fetchData = async () => {
const response = await fetch('/api/data');
return response.json();
};
// Méthode de classe
class ApiService {
async getData() {
const response = await fetch('/api/data');
return response.json();
}
}
// IIFE async (exécution immédiate)
(async () => {
const data = await fetchData();
console.log(data);
})();
Règles fondamentales
awaitne peut être utilisé que dans une fonctionasync(ou au top-level des modules ES)- Une fonction
asyncretourne toujours une Promise awaitmet en pause l'exécution de la fonction, pas du thread entier
Gestion des erreurs avec try/catch
// Pattern recommandé
async function createUser(userData) {
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || `HTTP ${response.status}`);
}
const user = await response.json();
return { success: true, data: user };
} catch (error) {
console.error('Erreur création user:', error);
return { success: false, error: error.message };
}
}
// Utilisation
const result = await createUser({ nom: 'Jean', email: 'jean@mail.com' });
if (result.success) {
showSuccess('Utilisateur créé !');
} else {
showError(result.error);
}
Erreurs granulaires
async function processPayment(orderId) {
let order, payment, receipt;
try {
order = await fetchOrder(orderId);
} catch (error) {
throw new Error(`Commande introuvable: ${error.message}`);
}
try {
payment = await chargeCard(order.total, order.cardToken);
} catch (error) {
throw new Error(`Paiement échoué: ${error.message}`);
}
try {
receipt = await generateReceipt(payment.id);
await sendReceiptEmail(order.email, receipt);
} catch (error) {
// Le paiement est passé, mais le reçu a échoué
console.warn('Reçu non envoyé:', error);
// On ne throw pas - le paiement reste valide
}
return { order, payment, receipt };
}
Exécution parallèle vs séquentielle
Séquentiel (quand l'ordre compte)
// Chaque await attend le précédent
async function sequential() {
const user = await fetchUser(1); // 500ms
const posts = await fetchPosts(user.id); // 300ms
const comments = await fetchComments(posts[0].id); // 200ms
// Total : ~1000ms (séquentiel)
return { user, posts, comments };
}
Parallèle avec Promise.all (quand l'ordre ne compte pas)
// Toutes les requêtes démarrent en même temps
async function parallel() {
const [users, posts, stats] = await Promise.all([
fetchUsers(), // 500ms
fetchPosts(), // 300ms
fetchStats() // 400ms
]);
// Total : ~500ms (le plus long des trois)
return { users, posts, stats };
}
// ATTENTION : si une seule échoue, tout échoue
// Alternative : Promise.allSettled
async function parallelSafe() {
const results = await Promise.allSettled([
fetchUsers(),
fetchPosts(),
fetchStats()
]);
return results.map((r, i) => ({
source: ['users', 'posts', 'stats'][i],
success: r.status === 'fulfilled',
data: r.status === 'fulfilled' ? r.value : null,
error: r.status === 'rejected' ? r.reason : null
}));
}
Promise.race et Promise.any
// Promise.race : le premier résolu (ou rejeté)
const fastest = await Promise.race([
fetch('https://api1.example.com/data'),
fetch('https://api2.example.com/data'),
]);
// Promise.any : le premier résolu (ignore les rejets)
const firstSuccess = await Promise.any([
fetch('https://cdn1.example.com/file'),
fetch('https://cdn2.example.com/file'),
fetch('https://cdn3.example.com/file'),
]);
Patterns avancés async/await
Retry avec backoff exponentiel
async function fetchWithRetry(url, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
if (attempt === maxRetries) throw error;
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
console.log(`Tentative ${attempt + 1} échouée, retry dans ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
Boucle async (traitement séquentiel d'un tableau)
// for...of avec await (séquentiel)
async function processItems(items) {
const results = [];
for (const item of items) {
const result = await processItem(item);
results.push(result);
}
return results;
}
// map + Promise.all (parallèle)
async function processItemsParallel(items) {
const results = await Promise.all(
items.map(item => processItem(item))
);
return results;
}
// Parallèle avec concurrence limitée
async function processWithLimit(items, limit = 5) {
const results = [];
for (let i = 0; i < items.length; i += limit) {
const batch = items.slice(i, i + limit);
const batchResults = await Promise.all(
batch.map(item => processItem(item))
);
results.push(...batchResults);
}
return results;
}
Timeout pour les Promises
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timeout après ${ms}ms`)), ms)
);
return Promise.race([promise, timeout]);
}
// Utilisation
try {
const data = await withTimeout(fetch('/api/slow-endpoint'), 5000);
} catch (error) {
if (error.message.includes('Timeout')) {
console.log('La requête a pris trop de temps');
}
}
Erreurs courantes à éviter
// ERREUR 1 : await dans forEach (ne fonctionne PAS)
items.forEach(async (item) => {
await processItem(item); // S'exécute en parallèle, pas séquentiel !
});
// SOLUTION : utiliser for...of
for (const item of items) { await processItem(item); }
// ERREUR 2 : oublier await
async function bad() {
const data = fetch('/api/data'); // Oubli de await !
console.log(data); // Promise, pas les données
}
// ERREUR 3 : await séquentiel inutile
const a = await fetchA(); // Attend A
const b = await fetchB(); // Puis attend B (inutile si indépendants)
// SOLUTION : paralléliser
const [a, b] = await Promise.all([fetchA(), fetchB()]);
Conclusion
async/await est la manière standard et recommandée de gérer l'asynchrone en JavaScript en 2026. Avec une bonne gestion des erreurs, la parallélisation via Promise.all, et les patterns avancés comme le retry, vous écrirez du code asynchrone robuste et performant.
Chez Tourak Digital, nous développons des applications web performantes avec les meilleures pratiques JavaScript. Parlons de votre projet.