Concurrence et Multithreading en TypeScript : de la théorie à la pratique
Les applications web modernes exigent des performances élevées. TypeScript, fonctionnant sur Node.js, fournit des outils puissants pour traiter plusieurs tâches simultanément. Dans cet article, nous allons voir comment fonctionnent la concurrence et le multithreading en TypeScript, et apprendre à les appliquer en pratique.
Concurrence vs Parallélisme : différences clés
Avant de plonger dans le code, il est important de comprendre la différence entre concurrence et parallélisme.
- Concurrence — c'est la capacité d'un programme à traiter plusieurs tâches en basculant entre elles. En JavaScript/TypeScript, cela est implémenté via l'Event Loop et les opérations asynchrones.
- Parallélisme — c'est l'exécution simultanée de plusieurs tâches sur différents cœurs de processeur. Dans Node.js, cela est réalisé via les Worker Threads.
TypeScript (comme JavaScript) est monothread par nature, mais grâce à l'asynchronisme et aux Worker Threads, nous pouvons utiliser efficacement les deux approches.
Asynchronisme : le fondement de la concurrence en TypeScript
La programmation asynchrone est la première étape vers la concurrence. Examinons les mécanismes principaux.
Promesses et async/await
Le TypeScript moderne utilise activement async/await pour travailler avec des opérations asynchrones. Cela permet d'écrire un code qui semble synchrone, mais qui s'exécute de manière concurrente.
// Exemple de fonction asynchroneasync function fetchData(url: string): Promise<any> { const response = await fetch(url); return response.json();}
// Lancement concurrent de plusieurs requêtesasync function fetchAllData(): Promise<void> { const [users, posts, comments] = await Promise.all([ fetchData('/api/users'), fetchData('/api/posts'), fetchData('/api/comments') ]); console.log('Toutes les données chargées de manière concurrente');}Promise.all permet de lancer plusieurs opérations asynchrones simultanément, ce qui accélère considérablement l'exécution par rapport à un lancement séquentiel.
Gestion des erreurs dans le code asynchrone
Il est important de gérer correctement les erreurs lors de l'exécution concurrente :
async function safeFetchAll(): Promise<void> { try { const results = await Promise.allSettled([ fetchData('/api/users'), fetchData('/api/invalid'), fetchData('/api/posts') ]); results.forEach((result, index) => { if (result.status === 'fulfilled') { console.log(`Requête ${index} réussie :`, result.value); } else { console.error(`Requête ${index} échouée :`, result.reason); } }); } catch (error) { console.error('Erreur critique :', error); }}Worker Threads : le vrai multithreading en TypeScript
Pour les tâches intensives en CPU (traitement d'images, calculs complexes), l'asynchronisme seul ne suffit pas. C'est là que les Worker Threads entrent en jeu — de véritables threads d'exécution.
Création d'un Worker Thread
Prenons l'exemple du calcul des nombres de Fibonacci dans un thread séparé :
// fibonacci.worker.tsimport { parentPort } from 'worker_threads';
function fibonacci(n: number): number { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2);}
parentPort?.on('message', (n: number) => { const result = fibonacci(n); parentPort?.postMessage(result);});// main.tsimport { Worker } from 'worker_threads';import path from 'path';
function runFibonacci(n: number): Promise<number> { return new Promise((resolve, reject) => { const worker = new Worker(path.resolve(__dirname, 'fibonacci.worker.ts')); worker.postMessage(n); worker.on('message', (result: number) => { resolve(result); worker.terminate(); }); worker.on('er
```