JavaScript et la concurrence : threads, goroutines et async/await sans maux de tête

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Un seul thread pour les gouverner tous : mythes et vérités sur la concurrence en JavaScript

\n\n

Il était une fois, à l'ère des sites statiques et des boutons « Envoyer » qui bloquaient toute l'interface, JavaScript vivait heureux dans un seul thread. Cela lui suffisait : une requête arrivait – elle était exécutée, une requête partait – l'interface se figeait. Mais les temps ont changé. Aujourd'hui, nous attendons des applications web une réaction instantanée : le défilement ne doit pas ralentir, les animations doivent être fluides, et le chargement des données, imperceptible. C'est là que la concurrence entre en jeu.

\n\n

De nombreux développeurs confondent la concurrence (concurrency) avec le parallélisme (parallelism). En JavaScript, surtout dans le navigateur, nous avons le plus souvent affaire à la première. C'est comme un jongleur : il ne maintient pas toutes les balles en l'air simultanément (c'est le parallélisme), mais il passe de l'une à l'autre avec tant d'habileté qu'il crée l'illusion de la simultanéité. Dans cet article, nous allons analyser trois outils clés pour organiser la concurrence en JS : async/await, les goroutines (via les générateurs et les Web Workers) et les threads classiques. C'est parti !

\n\n

Async/Await : un code asynchrone élégant

\n\n

async/await est un sucre syntaxique autour des promesses (Promises), apparu avec ES2017. Il ne rend pas le code multithread, mais il le rend asynchrone. Lorsque vous écrivez await, JavaScript « s'endort » dans cette fonction, libérant le thread pour d'autres tâches. Dès que la promesse est résolue, la fonction se réveille et continue son exécution. C'est l'outil idéal pour les opérations d'E/S : requêtes API, lecture de fichiers, temporisateurs.

\n\n

Exemple simple : chargement de données depuis un serveur

\n\n
// Fonction qui retourne une Promise\nfunction fetchUser(id) {\n    return new Promise((resolve) => {\n        setTimeout(() => resolve({ id, name: 'Alexeï' }), 1000);\n    });\n}\n\n// Fonction asynchrone\nasync function loadUserProfile(userId) {\n    console.log('Début du chargement...');\n    const user = await fetchUser(userId);\n    console.log('Utilisateur chargé :', user.name);\n    return user;\n}\n\nloadUserProfile(42);\nconsole.log('Ce code s'exécutera immédiatement, sans attendre le chargement !');\n
\n\n

Remarquez : console.log('Ce code s'exécutera immédiatement...') s'exécutera avant console.log('Utilisateur chargé'). Le thread n'est pas bloqué – c'est la concurrence en action.

\n\n

Gestion des erreurs

\n\n
async function fetchWithRetry(url, retries = 3) {\n    for (let i = 0; i < retries; i++) {\n        try {\n            const response = await fetch(url);\n            if (!response.ok) throw new Error('Erreur HTTP');\n            return await response.json();\n        } catch (error) {\n            if (i === retries - 1) throw error;\n            console.log(`Tentative ${i + 1} échouée, nouvel essai...`);\n        }\n    }\n}\n
\n\n

Quand l'utiliser : toutes les opérations avec des ressources externes (API, bases de données, système de fichiers dans Node.js). async/await est votre outil principal pour le code asynchrone.

\n\n

Les goroutines en JavaScript : mythe ou réalité ?

\n\n

Le terme « goroutine » vient de Go et désigne un thread d'exécution léger. JavaScript n'a pas de goroutines natives, mais il possède des générateurs (Generator Functions) et des Web Workers qui peuvent imiter un comportement similaire. Les générateurs permettent de suspendre et de reprendre l'exécution d'une fonction, offrant ainsi un contrôle sur le flux d'exécution.

\n\n

Les générateurs comme base de la concurrence

\n\n
function* taskGenerator() {\n    console.log('Lancement de la tâche 1');\n    yield 'Tâche 1 terminée';\n    console.log('Lancement de la tâche 2');\n    yield 'Tâche 2 terminée';\n    console.log('Lancement de la tâche 3');\n    yield 'Tâche 3 terminée';\n}\n\nconst iterator = taskGenerator();\nconsole.log(iterator.next().value); // Exécute la tâche 1\n// À ce moment, on peut faire autre chose\nconsole.log(iterator.next().value); // Exécute la tâche 2\nconsol                

Blogs

Book Recommendations