Maîtrisez l'itération asynchrone en JavaScript avec la boucle 'for await...of' et des utilitaires d'itérateurs asynchrones personnalisés. Améliorez le traitement des flux et la gestion des données avec des exemples pratiques.
Utilitaire d'Itérateur Asynchrone JavaScript : For Each - Itération sur les Flux
La programmation asynchrone est une pierre angulaire du développement JavaScript moderne, permettant aux applications de gérer des opérations chronophages sans bloquer le thread principal. Les itérateurs asynchrones, introduits dans ECMAScript 2018, fournissent un mécanisme puissant pour traiter les flux de données de manière asynchrone. Cet article de blog explore le concept des itérateurs asynchrones et montre comment implémenter une fonction d'assistance 'for each' asynchrone pour simplifier le traitement des flux.
Comprendre les Itérateurs Asynchrones
Un itérateur asynchrone est un objet qui se conforme à l'interface AsyncIterator. Il définit une méthode next() qui renvoie une promesse, laquelle se résout en un objet avec deux propriétés :
value: La prochaine valeur dans la séquence.done: Un booléen indiquant si l'itérateur est terminé.
Les itérateurs asynchrones sont couramment utilisés pour consommer des données provenant de sources asynchrones telles que les flux réseau, les systèmes de fichiers ou les bases de données. La boucle for await...of fournit une syntaxe pratique pour itérer sur les itérables asynchrones.
Exemple : Lecture Asynchrone d'un Fichier
Imaginez un scénario où vous devez lire un gros fichier ligne par ligne sans bloquer le thread principal. Vous pouvez y parvenir en utilisant un itérateur asynchrone :
const fs = require('fs');
const readline = require('readline');
async function* readFileLines(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
yield line;
}
}
async function processFile(filePath) {
for await (const line of readFileLines(filePath)) {
console.log(`Line: ${line}`);
}
}
// Exemple d'utilisation
processFile('path/to/your/file.txt');
Dans cet exemple, readFileLines est une fonction génératrice asynchrone qui produit chaque ligne du fichier au fur et à mesure de sa lecture. La fonction processFile itère ensuite sur les lignes en utilisant for await...of, traitant chaque ligne de manière asynchrone.
Implémenter un Utilitaire 'For Each' Asynchrone
Bien que la boucle for await...of soit utile, elle peut devenir verbeuse lorsque vous devez effectuer des opérations complexes sur chaque élément du flux. Une fonction d'assistance 'for each' asynchrone peut simplifier ce processus en encapsulant la logique d'itération.
Implémentation de Base
Voici une implémentation de base d'une fonction 'for each' asynchrone :
async function asyncForEach(iterable, callback) {
for await (const item of iterable) {
await callback(item);
}
}
Cette fonction prend un itérable asynchrone et une fonction de rappel (callback) en arguments. Elle itère sur l'itérable en utilisant for await...of et appelle la fonction de rappel pour chaque élément. La fonction de rappel doit également être asynchrone si vous souhaitez attendre sa complétion avant de passer à l'élément suivant.
Exemple : Traitement des Données d'une API
Supposons que vous récupérez des données d'une API qui renvoie un flux d'éléments. Vous pouvez utiliser l'utilitaire 'for each' asynchrone pour traiter chaque élément à son arrivée :
async function* fetchDataStream(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
return;
}
// En supposant que l'API renvoie des morceaux de JSON
const chunk = decoder.decode(value);
const items = JSON.parse(`[${chunk.replace(/\}\{/g, '},{')}]`); //Diviser les morceaux en un tableau JSON valide
for(const item of items){
yield item;
}
}
} finally {
reader.releaseLock();
}
}
async function processItem(item) {
// Simuler une opération asynchrone
await new Promise(resolve => setTimeout(resolve, 100));
console.log(`Processing item: ${JSON.stringify(item)}`);
}
async function main() {
const apiUrl = 'https://api.example.com/data'; // Remplacez par le point de terminaison de votre API
await asyncForEach(fetchDataStream(apiUrl), processItem);
console.log('Finished processing data.');
}
// Exemple d'utilisation
main();
Dans cet exemple, fetchDataStream récupère les données de l'API et produit chaque élément au fur et à mesure de sa réception. La fonction processItem simule une opération asynchrone sur chaque élément. L'utilitaire asyncForEach simplifie ensuite la logique d'itération et de traitement.
Améliorations et Considérations
Gestion des Erreurs
Il est crucial de gérer les erreurs qui могут survenir lors de l'itération asynchrone. Vous pouvez envelopper la fonction de rappel dans un bloc try...catch pour attraper et gérer les exceptions :
async function asyncForEach(iterable, callback) {
for await (const item of iterable) {
try {
await callback(item);
} catch (error) {
console.error(`Error processing item: ${item}`, error);
// Vous pouvez choisir de relancer l'erreur ou de continuer le traitement
}
}
}
Contrôle de la Concurrence
Par défaut, l'utilitaire 'for each' asynchrone traite les éléments séquentiellement. Si vous avez besoin de traiter les éléments simultanément, vous pouvez utiliser un pool de Promesses pour limiter le nombre d'opérations concurrentes :
async function asyncForEachConcurrent(iterable, callback, concurrency) {
const executing = [];
for await (const item of iterable) {
const p = callback(item).then(() => executing.splice(executing.indexOf(p), 1));
executing.push(p);
if (executing.length >= concurrency) {
await Promise.race(executing);
}
}
await Promise.all(executing);
}
async function processItem(item) {
// Simuler une opération asynchrone
await new Promise(resolve => setTimeout(resolve, 100));
console.log(`Processing item: ${JSON.stringify(item)}`);
}
async function main() {
const apiUrl = 'https://api.example.com/data'; // Remplacez par le point de terminaison de votre API
await asyncForEachConcurrent(fetchDataStream(apiUrl), processItem, 5); // Concurrence de 5
console.log('Finished processing data.');
}
Dans cet exemple, asyncForEachConcurrent limite le nombre d'exécutions de rappel simultanées au niveau de concurrence spécifié. Cela peut améliorer les performances lors du traitement de grands flux de données.
Annulation
Dans certains cas, vous pourriez avoir besoin d'annuler le processus d'itération prématurément. Vous pouvez y parvenir en utilisant un AbortController :
async function asyncForEach(iterable, callback, signal) {
for await (const item of iterable) {
if (signal && signal.aborted) {
console.log('Iteration aborted.');
return;
}
await callback(item);
}
}
async function main() {
const controller = new AbortController();
const signal = controller.signal;
setTimeout(() => {
controller.abort(); // Annuler après 2 secondes
}, 2000);
const apiUrl = 'https://api.example.com/data'; // Remplacez par le point de terminaison de votre API
await asyncForEach(fetchDataStream(apiUrl), processItem, signal);
console.log('Finished processing data.');
}
Dans cet exemple, la fonction asyncForEach vérifie la propriété signal.aborted avant chaque itération. Si le signal est annulé, l'itération est arrêtée.
Applications Concrètes
Les itérateurs asynchrones et l'utilitaire 'for each' asynchrone peuvent être appliqués à un large éventail de scénarios réels :
- Pipelines de traitement de données : Traitement de grands ensembles de données provenant de bases de données ou de systèmes de fichiers.
- Flux de données en temps réel : Gestion des données provenant de Web Sockets, de files d'attente de messages ou de réseaux de capteurs.
- Consommation d'API : Récupération et traitement de données d'API qui renvoient des flux d'éléments.
- Traitement d'images et de vidéos : Traitement de grands fichiers multimédias par morceaux.
- Analyse de logs : Analyse de grands fichiers de logs ligne par ligne.
Exemple - Données Boursières Internationales : Considérez une application qui récupère en temps réel les cotations boursières de diverses places boursières internationales. Un itérateur asynchrone peut être utilisé pour streamer les données, et un 'for each' asynchrone peut traiter chaque cotation, mettant à jour l'interface utilisateur avec les derniers prix. Cela peut être utilisé pour afficher les cours actuels des actions de sociétés comme :
- Tencent (Chine) : Récupération des données boursières d'une grande entreprise technologique internationale
- Tata Consultancy Services (Inde) : Affichage des mises à jour boursières d'une entreprise leader dans les services informatiques
- Samsung Electronics (Corée du Sud) : Présentation des cours de l'action d'un fabricant mondial d'électronique
- Toyota Motor Corporation (Japon) : Suivi du cours des actions d'un constructeur automobile international
Conclusion
Les itérateurs asynchrones et l'utilitaire 'for each' asynchrone offrent un moyen puissant et élégant de traiter les flux de données de manière asynchrone en JavaScript. En encapsulant la logique d'itération, vous pouvez simplifier votre code, améliorer la lisibilité et augmenter les performances de vos applications. En gérant les erreurs, en contrôlant la concurrence et en permettant l'annulation, vous pouvez créer des pipelines de traitement de données asynchrones robustes et évolutifs.