La programmation asynchrone est un paradigme de programmation qui vous permet d’écrire du code qui s’exécute de manière asynchrone
. Contrairement à la programmation synchrone, qui exécute le code de manière séquentielle, la programmation asynchrone permet au code de s’exécuter en arrière-plan pendant que le reste du programme continue de s’exécuter. Cela est particulièrement utile pour les tâches qui peuvent prendre du temps à se terminer, telles que la récupération de données à partir d’une API distante.
La programmation asynchrone
est essentielle pour créer des applications réactives et efficaces en JavaScript. TypeScript, un sur-ensemble de JavaScript, facilite encore plus le travail avec la programmation asynchrone.
Il existe plusieurs approches de la programmation asynchrone
en TypeScript, y compris l’utilisation de promesses
, async/await
et callbacks
. Nous couvrirons chacune de ces approches en détail afin que vous puissiez choisir la ou les meilleures pour votre cas d’utilisation.
Table des matières
Pourquoi la programmation asynchrone est-elle importante ?
La programmation asynchrone est cruciale pour construire des applications web réactives et efficaces. Elle permet aux tâches de s’exécuter en arrière-plan pendant que le reste du programme continue, maintenant l’interface utilisateur réactive aux entrées. De plus, la programmation asynchrone peut améliorer les performances globales en permettant à plusieurs tâches de s’exécuter en même temps.
Il existe de nombreux exemples concrets de programmation asynchrone, tels que l’accès aux caméras et microphones des utilisateurs et la gestion des événements d’entrée utilisateur. Même si vous ne créez pas fréquemment des fonctions asynchrones, il est important de savoir comment les utiliser correctement pour vous assurer que votre application est fiable et performante.
Comment TypeScript facilite la programmation asynchrone
TypeScript offre plusieurs fonctionnalités qui simplifient la programmation asynchrone, notamment la sécurité des types
, l’inférence de types
, la vérification des types
et les annotations de type
.
Avec la sécurité des types, vous pouvez garantir que votre code se comporte comme prévu, même lors de la manipulation de fonctions asynchrones. Par exemple, TypeScript peut détecter les erreurs liées aux valeurs nulles et indéfinies lors de la compilation, ce qui vous fait gagner du temps et des efforts de débogage.
L’inférence et la vérification des types de TypeScript réduisent également la quantité de code redondant que vous devez écrire, rendant votre code plus concis et plus facile à lire.
Et les annotations de type de TypeScript fournissent de la clarté et de la documentation pour votre code, ce qui est particulièrement utile lorsque vous travaillez avec des fonctions asynchrones qui peuvent être complexes à comprendre.
Maintenant, plongeons-nous et apprenons ces trois fonctionnalités clés de la programmation asynchrone : les promesses, async/await et les rappels.
Comment utiliser les promesses en TypeScript
Les promesses sont un outil puissant pour gérer les opérations asynchrones en TypeScript. Par exemple, vous pourriez utiliser une promesse pour récupérer des données à partir d’une API externe ou pour effectuer une tâche chronophage en arrière-plan pendant que votre thread principal continue de s’exécuter.
Pour utiliser une promesse, vous créez une nouvelle instance de la classe Promise
et lui passez une fonction qui effectue l’opération asynchrone. Cette fonction devrait appeler la méthode resolve avec le résultat lorsque l’opération réussit ou la méthode reject avec une erreur en cas d’échec.
Une fois la promesse créée, vous pouvez attacher des rappels à celle-ci en utilisant la méthode then
. Ces rappels seront déclenchés lorsque la promesse est accomplie, avec la valeur résolue passée en paramètre. Si la promesse est rejetée, vous pouvez attacher un gestionnaire d’erreurs en utilisant la méthode catch, qui sera appelée avec la raison du rejet.
L’utilisation des promesses offre plusieurs avantages par rapport aux méthodes basées sur les rappels traditionnels. Par exemple, les promesses peuvent aider à éviter l' »enfer des rappels », un problème courant dans le code asynchrone où les rappels imbriqués deviennent difficiles à lire et à maintenir.
Les promesses facilitent également la gestion des erreurs dans le code asynchrone, car vous pouvez utiliser la méthode catch pour gérer les erreurs qui se produisent n’importe où dans la chaîne de promesses.
Enfin, les promesses peuvent simplifier votre code en fournissant un moyen cohérent et composable de gérer les opérations asynchrones, quelle que soit leur implémentation sous-jacente.
Comment créer une promesse
Syntaxe de la promesse :
const myPromise = new Promise((resolve, reject) => {
// Effectuer une opération asynchrone
// Si l'opération réussit, appeler resolve avec le résultat
// Si l'opération échoue, appeler reject avec un objet d'erreur
});
myPromise
.then((result) => {
// Gérer le résultat réussi
})
.catch((error) => {
// Gérer l'erreur
});
// Exemple 1 sur la création d'une promesse
function myAsyncFunction(): Promise<string> {
return new Promise<string>((resolve, reject) => {
// Quelques opérations asynchrones
setTimeout(() => {
// L'opération réussie résout la promesseConsultez mon dernier article de blog sur la maîtrise de la programmation asynchrone en TypeScript ! Apprenez à travailler avec les Promesses, Async/Await et les Callbacks pour écrire un code efficace et évolutif. Préparez-vous à faire évoluer vos compétences en TypeScript au niveau supérieur !
const success = true;
if (success) {
// Résoudre la promesse avec le résultat de l'opération si l'opération a réussi
resolve(
`The result is success and your operation result is ${operationResult}`
);
} else {
const rejectCode: number = 404;
const rejectMessage: string = `The result is failed and your operation result is ${rejectCode}`;
// Rejeter la promesse avec le résultat de l'opération si l'opération a échoué
reject(new Error(rejectMessage));
}
}, 2000);
});
}
// Utiliser la promesse
myAsyncFunction()
.then((result) => {
console.log(result); // sortie : Le résultat est un succès et votre résultat de l'opération est 4
})
.catch((error) => {
console.error(error); // sortie : Le résultat a échoué et votre résultat de l'opération est 404
});
Dans l’exemple ci-dessus, nous avons une fonction appelée myAsyncFunction()
qui renvoie une promise
. Nous utilisons le constructeur Promise
pour créer la promesse, qui prend une fonction de rappel
avec les arguments resolve
et reject
. Si l’opération asynchrone réussit, nous appelons la fonction resolve. S’il échoue, nous appelons la fonction reject.
L’objet promesse renvoyé par le constructeur a une méthode then()
, qui prend des fonctions de rappel de succès et d’échec. Si la promesse est résolue avec succès, la fonction de rappel de succès est appelée avec le résultat. Si la promesse est rejetée, la fonction de rappel d’échec est appelée avec un message d’erreur.
L’objet promesse a également une méthode catch()
utilisée pour gérer les erreurs qui se produisent pendant la chaîne de promesses. La méthode catch()
prend une fonction de rappel, qui est appelée si une erreur se produit dans la chaîne de promesses.
Maintenant, passons à la façon de chaîner des promesses en TypeScript.
Comment Chaîner des Promesses
La chaînage des promesses vous permet d’effectuer plusieurs opérations asynchrones
en séquence ou en parallèle. Cela est utile lorsque vous avez besoin d’effectuer plusieurs tâches asynchrones les unes après les autres ou en même temps. Par exemple, vous pourriez avoir besoin de récupérer des données de manière asynchrone puis de les traiter de manière asynchrone.
Examinons un exemple de comment chaîner des promesses:
// Exemple sur le fonctionnement de la chaîne de promesses
// Première promesse
const promise1 = new Promise((resolve, reject) => {
const functionOne: string = "This is the first promise function";
setTimeout(() => {
resolve(functionOne);
}, 1000);
});
// Deuxième promesse
const promise2 = (data: number) => {
const functionTwo: string = "This is the second second promise function";
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(` ${data} '+' ${functionTwo} `);
}, 1000);
});
};
// Chaînage des première et deuxième promesses ensemble
promise1
.then(promise2)
.then((result) => {
console.log(result); // sortie : Ceci est la fonction de la première promesse + Ceci est la fonction de la deuxième promesse
})
.catch((error) => {
console.error(error);
});
Dans l’exemple ci-dessus, nous avons deux promesses : promise1
et promise2
. promise1
se résout après 1 seconde avec la chaîne « Ceci est la fonction de la première promesse. » promise2
prend un nombre en entrée et retourne une promesse qui se résout après 1 seconde avec une chaîne qui combine le nombre d’entrée et la chaîne « Ceci est la fonction de la deuxième promesse. »
Nous chaînons les deux promesses ensemble en utilisant la méthode then
. La sortie de promise1
est passée en entrée à promise2
. Enfin, nous utilisons à nouveau la méthode then
pour journaliser la sortie de promise2
dans la console. Si promise1
ou promise2
est rejetée, l’erreur sera capturée par la méthode catch
.
Félicitations ! Vous avez appris à créer et chaîner des promesses en TypeScript. Vous pouvez maintenant utiliser des promesses pour effectuer des opérations asynchrones en TypeScript. Maintenant, explorons comment fonctionne Async/Await
en TypeScript.
Comment utiliser Async / Await en TypeScript
Async/await est une syntaxe introduite en ES2017 pour faciliter le travail avec les Promises. Cela vous permet d’écrire du code asynchrone qui semble et se comporte comme du code synchrone.
En TypeScript, vous pouvez définir une fonction asynchrone en utilisant le mot-clé async
. Cela indique au compilateur que la fonction est asynchrone et renverra une Promise.
Maintenant, voyons comment utiliser async/await en TypeScript.
Syntaxe Async/Await:
// Syntaxe Async/Await en TypeScript
async function functionName(): Promise<ReturnType> {
try {
const result = await promise;
// code à exécuter après la résolution de la promesse
return result;
} catch (error) {
// code à exécuter si la promesse est rejetée
throw error;
}
}
Dans l’exemple ci-dessus, functionName
est une fonction asynchrone qui renvoie une Promise de ReturnType
. Le mot-clé await
est utilisé pour attendre que la promesse se résolve avant de passer à la ligne de code suivante.
Le bloc try/catch
est utilisé pour gérer les erreurs qui se produisent lors de l’exécution du code à l’intérieur de la fonction asynchrone. Si une erreur se produit, elle sera capturée par le bloc catch, où vous pouvez la gérer de manière appropriée.
Utilisation des fonctions fléchées avec Async/Await
Vous pouvez également utiliser des fonctions fléchées avec la syntaxe async/await en TypeScript:
const functionName = async (): Promise<ReturnType> => {
try {
const result = await promise;
// code à exécuter après la résolution de la promesse
return result;
} catch (error) {
// code à exécuter si la promesse est rejetée
throw error;
}
};
Dans l’exemple ci-dessus, functionName
est défini en tant que fonction fléchée qui renvoie une Promise de ReturnType
. Le mot-clé async indique qu’il s’agit d’une fonction asynchrone, et le mot-clé await est utilisé pour attendre que la promesse se résolve avant de passer à la ligne de code suivante.
Async/Await avec un appel API
Maintenant, allons au-delà de la syntaxe et récupérons des données à partir d’une API en utilisant async/await.
interface User {
id: number;
name: string;
email: string;
}
const fetchApi = async (): Promise<void> => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) {
throw new Error(
`Failed to fetch users (HTTP status code: ${response.status})`
);
}
const data: User[] = await response.json();
console.log(data);
} catch (error) {
console.error(error);
throw error;
}
};
fetchApi();
Ici, nous récupérons des données de l’API JSONPlaceholder, les convertissons en JSON, puis les affichons dans la console. Il s’agit d’un exemple concret de l’utilisation d’async/await en TypeScript.
Vous devriez voir les informations de l’utilisateur dans la console. Cette image montre la sortie:
Async/Await avec un appel API Axios
// Exemple 2 sur l'utilisation de async/await en TypeScript
const fetchApi = async (): Promise<void> => {
try {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/users"
);
const data = await response.data;
console.log(data);
} catch (error) {
console.error(error);
}
};
fetchApi();
Dans l’exemple ci-dessus, nous définissons la fonction fetchApi()
en utilisant async/await et la méthode Axios.get()
pour effectuer une requête HTTP GET à l’URL spécifiée. Nous utilisons await pour attendre la réponse, puis nous extrayons les données en utilisant la propriété data de l’objet réponse. Enfin, nous affichons les données dans la console avec console.log()
. Toutes les erreurs survenues sont capturées et affichées dans la console avec console.error()
.
Nous pouvons réaliser cela en utilisant Axios, donc vous devriez voir le même résultat dans la console.
Cette image montre la sortie lorsque nous utilisons Axios dans la console:
Remarque : Avant d’essayer le code ci-dessus, vous devez installer Axios en utilisant npm ou yarn.
npm install axios
yarn add axios
Si vous n’êtes pas familier avec Axios, vous pouvez en apprendre davantage à ce sujet ici.
Vous pouvez voir que nous avons utilisé un bloc try
et catch
pour gérer les erreurs. Le bloc try
et catch
est une méthode pour gérer les erreurs en TypeScript. Donc, chaque fois que vous effectuez des appels API comme nous venons de le faire, assurez-vous d’utiliser un bloc try
et catch
pour gérer les erreurs éventuelles.
Maintenant, explorons une utilisation plus avancée du bloc try
et catch
en TypeScript :
// Exemple 3 sur comment utiliser async / await en TypeScript
interface Recipe {
id: number;
name: string;
ingredients: string[];
instructions: string[];
prepTimeMinutes: number;
cookTimeMinutes: number;
servings: number;
difficulty: string;
cuisine: string;
caloriesPerServing: number;
tags: string[];
userId: number;
image: string;
rating: number;
reviewCount: number;
mealType: string[];
}
const fetchRecipes = async (): Promise<Recipe[] | string> => {
const api = "https://dummyjson.com/recipes";
try {
const response = await fetch(api);
if (!response.ok) {
throw new Error(`Failed to fetch recipes: ${response.statusText}`);
}
const { recipes } = await response.json();
return recipes; // Renvoyer le tableau des recettes
} catch (error) {
console.error("Error fetching recipes:", error);
if (error instanceof Error) {
return error.message;
}
return "An unknown error occurred.";
}
};
// Récupérer et afficher les recettes
fetchRecipes().then((data) => {
if (Array.isArray(data)) {
console.log("Recipes fetched successfully:", data);
} else {
console.error("Error message:", data);
}
});
Dans l’exemple ci-dessus, nous définissons une interface Recette
qui décrit la structure des données que nous attendons de l’API. Ensuite, nous créons la fonction fetchRecipes()
en utilisant async/await et la méthode fetch() pour effectuer une requête HTTP GET vers le point de terminaison API spécifié.
Nous utilisons un bloc try/catch
pour gérer les erreurs qui pourraient survenir lors de la requête API. Si la requête réussit, nous extrayons la propriété de données de la réponse en utilisant await et la retournons. Si une erreur survient, nous vérifions un message d’erreur et le retournons sous forme de chaîne s’il existe.
Finalement, nous appelons la fonction fetchRecipes()
et utilisons .then()
pour afficher les données renvoyées dans la console. Cet exemple montre comment utiliser async/await
avec des blocs try/catch
pour gérer les erreurs dans un scénario plus avancé, où nous devons extraire des données d’un objet réponse et renvoyer un message d’erreur personnalisé.
Cette image montre le résultat de la sortie du code:
Async / Await avec Promise.all
Promise.all()
est une méthode qui prend un tableau de promesses en entrée (un itérable) et renvoie une seule promesse en sortie. Cette promesse se résout lorsque toutes les promesses d’entrée ont été résolues ou si l’itérable d’entrée ne contient aucune promesse. Elle est rejetée immédiatement si l’une des promesses d’entrée est rejetée ou si des erreurs sont générées par des éléments ne renvoyant pas de promesse, et elle sera rejetée avec le premier message de rejet ou erreur.
// Exemple d'utilisation de async/await avec Promise.all
interface User {
id: number;
name: string;
email: string;
profilePicture: string;
}
interface Post {
id: number;
title: string;
body: string;
}
interface Comment {
id: number;
postId: number;
name: string;
email: string;
body: string;
}
const fetchApi = async <T>(url: string): Promise<T> => {
try {
const response = await fetch(url);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Network response was not ok for ${url}`);
}
} catch (error) {
console.error(error);
throw new Error(`Error fetching data from ${url}`);
}
};
const fetchAllApis = async (): Promise<[User[], Post[], Comment[]]> => {
try {
const [users, posts, comments] = await Promise.all([
fetchApi<User[]>("https://jsonplaceholder.typicode.com/users"),
fetchApi<Post[]>("https://jsonplaceholder.typicode.com/posts"),
fetchApi<Comment[]>("https://jsonplaceholder.typicode.com/comments"),
]);
return [users, posts, comments];
} catch (error) {
console.error(error);
throw new Error("Error fetching data from one or more APIs");
}
};
fetchAllApis()
.then(([users, posts, comments]) => {
console.log("Users: ", users);
console.log("Posts: ", posts);
console.log("Comments: ", comments);
})
.catch((error) => console.error(error));
Dans le code ci-dessus, nous avons utilisé Promise.all
pour récupérer plusieurs API en même temps. Si vous avez plusieurs API à récupérer, vous pouvez utiliser Promise.all
pour les obtenir tous en une seule fois. Comme vous pouvez le voir, nous avons utilisé map
pour parcourir le tableau des API puis les avons passés à Promise.all
pour les récupérer simultanément.
L’image ci-dessous montre la sortie des appels API:
Voyons comment utiliser Promise.all
avec Axios:
// Exemple d'utilisation de async/await avec axios et Promise.all
const fetchApi = async () => {
try {
const urls = [
"https://jsonplaceholder.typicode.com/users",
"https://jsonplaceholder.typicode.com/posts",
];
const responses = await Promise.all(urls.map((url) => axios.get(url)));
const data = await Promise.all(responses.map((response) => response.data));
console.log(data);
} catch (error) {
console.error(error);
}
};
fetchApi();
Dans l’exemple ci-dessus, nous utilisons Promise.all
pour récupérer des données à partir de deux URL différentes en même temps. Tout d’abord, nous créons un tableau d’URL, puis utilisons la fonction map pour créer un tableau de Promises à partir des appels axios.get
. Nous passons ce tableau à Promise.all
, qui renvoie un tableau de réponses. Enfin, nous utilisons à nouveau la fonction map pour obtenir les données de chaque réponse et les afficher dans la console.
Comment utiliser les rappels en TypeScript
Un rappel est une fonction passée en argument à une autre fonction. La fonction de rappel est exécutée à l’intérieur de l’autre fonction. Les rappels garantissent qu’une fonction ne s’exécute pas avant qu’une tâche ne soit terminée, mais qu’elle s’exécute juste après la fin de la tâche. Ils nous aident à écrire du code JavaScript asynchrone et à prévenir les problèmes et les erreurs.
// Exemple d'utilisation des rappels en typescript
const add = (a: number, b: number, callback: (result: number) => void) => {
const result = a + b;
callback(result);
};
add(10, 20, (result) => {
console.log(result);
});
L’image ci-dessous montre la fonction de rappel:
Voyons un autre exemple d’utilisation des rappels en TypeScript:
// Exemple d'utilisation d'une fonction de rappel en TypeScript
type User = {
name: string;
email: string;
};
const fetchUserData = (
id: number,
callback: (error: Error | null, user: User | null) => void
) => {
const api = `https://jsonplaceholder.typicode.com/users/${id}`;
fetch(api)
.then((response) => {
if (response.ok) {
return response.json();
} else {
throw new Error("Network response was not ok.");
}
})
.then((data) => {
const user: User = {
name: data.name,
email: data.email,
};
callback(null, user);
})
.catch((error) => {
callback(error, null);
});
};
// Utilisation de fetchUserData avec une fonction de rappel
fetchUserData(1, (error, user) => {
if (error) {
console.error(error);
} else {
console.log(user);
}
});
Dans l’exemple ci-dessus, nous avons une fonction appelée fetchUserData
qui prend un id
et un callback
en tant que paramètres. Ce callback
est une fonction avec deux paramètres : une erreur et un utilisateur.
La fonction fetchUserData
récupère les données utilisateur à partir d’un point de terminaison de l’API JSONPlaceholder en utilisant l’id
. Si la récupération est réussie, elle crée un objet User
et le passe à la fonction de rappel avec une erreur nulle. S’il y a une erreur lors de la récupération, elle envoie l’erreur à la fonction de rappel avec un utilisateur nul.
Pour utiliser la fonction fetchUserData
avec un rappel, nous fournissons un id
et une fonction de rappel en tant qu’arguments. La fonction de rappel vérifie les erreurs et enregistre les données utilisateur s’il n’y a pas d’erreurs.
L’image ci-dessous montre la sortie des appels API:
Comment Utiliser les Rappels de Manière Responsable
Alors que les rappels sont fondamentaux pour la programmation asynchrone en TypeScript, ils nécessitent une gestion soigneuse pour éviter l’« enfer des rappels » – le code imbriqué en forme de pyramide qui devient difficile à lire et à entretenir. Voici comment utiliser les rappels de manière efficace:
-
Évitez les imbrications profondes
-
Aplatir la structure de votre code en découpant les opérations complexes en fonctions nommées
-
Utilisez des promesses ou async/await pour des flux de travail asynchrones complexes (voir ci-dessous)
-
-
Gestion des erreurs en premier
-
Suivez toujours la convention de Node.js des paramètres
(erreur, résultat)
-
Vérifiez les erreurs à chaque niveau des rappels imbriqués
-
function processData(input: string, callback: (err: Error | null, result?: string) => void) {
// ... appelez toujours d'abord le rappel avec l'erreur
}
-
Utilisez des annotations de type
-
Tirez parti du système de types de TypeScript pour imposer les signatures de rappel
-
Définissez des interfaces claires pour les paramètres de rappel
-
type ApiCallback = (error: Error | null, data?: ApiResponse) => void;
-
Considérez les bibliothèques de flux de contrôle
Pour des opérations asynchrones complexes, utilisez des utilitaires commeasync.js
pour :-
Exécution parallèle
-
Exécution en série
-
Gestion des pipelines d’erreurs
-
Quand utiliser les rappels par rapport aux alternatives
Il y a des moments où les rappels sont un excellent choix, et d’autres moments où ils ne le sont pas.
Les rappels sont utiles lorsque vous travaillez avec des opérations asynchrones (une seule complétion), interagissez avec d’anciennes bibliothèques ou API qui attendent des rappels, gérez des écouteurs d’événements (comme des écouteurs de clics ou des événements de websocket) ou créez des utilitaires légers avec des besoins asynchrones simples.
Dans d’autres scénarios où vous devez vous concentrer sur l’écriture d’un code maintenable avec un flux asynchrone clair, les rappels posent problème et vous devriez préférer les promesses ou async-await. Par exemple, lorsque vous avez besoin d’enchaîner plusieurs opérations, de gérer une propagation d’erreurs complexe, de travailler avec des API modernes (comme l’API Fetch ou les promesses FS), ou d’utiliser promise.all()
pour une exécution parallèle.
Exemple de migration des rappels vers les promesses :
// Version de rappel
function fetchUser(id: number, callback: (err: Error | null, user?: User) => void) {
// ...
}
// Version de promesse
async function fetchUserAsync(id: number): Promise<User> {
// ...
}
// Utilisation avec async/await
try {
const user = await fetchUserAsync(1);
} catch (error) {
// Gérer les erreurs
}
L’évolution des motifs asynchrones
Motif | Avantages | Inconvénients |
Rappels | Simple, universel | Complexité imbriquée |
Promesses | Chaînable, meilleure gestion des erreurs | Requiert des chaînes .then() |
Async/Await | Lisibilité semblable à la synchronie | Requiert une transpilation |
Les projets TypeScript modernes utilisent souvent un mélange : des rappels pour les motifs basés sur les événements et des promesses/async-await pour la logique asynchrone complexe. La clé est de choisir le bon outil pour votre cas d’utilisation spécifique tout en maintenant la clarté du code.
Conclusion
Dans cet article, nous avons appris les différentes façons de gérer le code asynchrone en TypeScript. Nous avons appris les rappels, les promesses, async-await, et comment les utiliser en TypeScript. Nous avons également appris ce concept.
Si vous souhaitez en apprendre davantage sur la programmation et comment devenir un meilleur ingénieur logiciel, vous pouvez vous abonner à ma chaîne YouTube CliffTech.
Merci d’avoir lu mon article. J’espère qu’il vous a plu. Si vous avez des questions, n’hésitez pas à me contacter.
Connectez-vous avec moi sur les réseaux sociaux :