Comprendre async/await
Si vous n'êtes pas très familier avec ECMAScript 2017, vous ne connaissez peut-être pas async/await. C'est un moyen utile de traiter les Promises de manière hissée. C'est aussi légèrement plus rapide et augmente la lisibilité générale.
Comment fonctionnent les Promises ?
Avant que nous puissions nous lancer dans async/await, vous devez savoir ce que sont les Promises et comment elles fonctionnent car async/await est juste un moyen de traiter les Promises. Si vous savez ce que sont les Promises et comment les gérer, vous pouvez sauter cette partie.
Les Promises sont un moyen de gérer les tâches asynchrones en JavaScript ; elles sont l'alternative plus récente aux rappels. Une Promise a beaucoup de similitudes avec une barre de progression ; elles représentent un processus inachevé et en cours. Un excellent exemple de ceci est une requête à un serveur (par exemple, discord.js envoie des requêtes à l'API Discord).
Une Promise peut avoir trois états ; en attente, résolu et rejeté.
- L'état en attente signifie que la Promise est toujours en cours et n'a été ni résolue ni rejetée.
- L'état résolu signifie que la Promise est terminée et exécutée sans erreurs.
- L'état rejeté signifie que la Promise a rencontré une erreur et n'a pas pu s'exécuter correctement.
Une chose importante à savoir est qu'une Promise ne peut avoir qu'un seul état simultanément ; elle ne peut jamais être en attente et résolue, rejetée et résolue, ou en attente et rejetée. Vous vous posez peut-être la question, "Comment cela ressemblerait-il dans le code ?" Voici un petit exemple :
Cet exemple utilise du code ES6. Si vous ne savez pas ce que c'est, vous devriez en lire plus ici.
function deleteMessages(amount) {
return new Promise((resolve, reject) => {
if (amount > 10) return reject(new Error("You can't delete more than 10 Messages at a time."));
setTimeout(() => resolve('Deleted 10 messages.'), 2_000);
});
}
deleteMessages(5)
.then((value) => {
// `deleteMessages` est terminé et n'a rencontré aucune erreur
// la valeur résolue sera la chaîne "Deleted 10 messages"
})
.catch((error) => {
// `deleteMessages` a rencontré une erreur
// l'erreur sera un objet Error
});Dans ce scénario, la fonction deleteMessages retourne une Promise. La méthode .then() se déclenchera si la Promise se résout, et la méthode .catch() si la Promise est rejetée. Dans la fonction deleteMessages, la Promise est résolue après 2 secondes avec la chaîne "Deleted 10 messages.", donc la méthode .catch() ne sera jamais exécutée. Vous pouvez également passer la fonction .catch() comme deuxième paramètre de .then().
Comment implémenter async/await
Théorie
Les informations suivantes sont essentielles à connaître avant de travailler avec async/await. Vous ne pouvez utiliser le mot-clé await que dans une fonction déclarée comme async (vous mettez le mot-clé async avant le mot-clé function ou avant les paramètres lors de l'utilisation d'une fonction callback).
Un exemple simple serait :
async function declaredAsAsync() {
// ...
}ou
const declaredAsAsync = async () => {
// ...
};Vous pouvez également l'utiliser si vous utilisez la fonction fléchée comme écouteur d'événement.
client.on('event', async (first, last) => {
// ...
});Une chose importante à savoir est qu'une fonction déclarée comme async retournera toujours une Promise. En plus de cela, si vous retournez quelque chose, la Promise se résoudra avec cette valeur, et si vous lancez une erreur, elle rejettera la Promise avec cette erreur.
Exécution avec du code discord.js
Maintenant que vous savez comment fonctionnent les Promises et à quoi elles servent, examinons un exemple qui gère plusieurs Promises. Supposons que vous vouliez réagir avec des lettres (indicateurs régionaux) dans un ordre spécifique. Pour cet exemple, voici un modèle de base pour un bot discord.js avec quelques ajustements ES6.
const { Client, Events, GatewayIntentBits } = require('discord.js');
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.once(Events.ClientReady, () => {
console.log('I am ready!');
});
client.on(Events.InteractionCreate, (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'react') {
// ...
}
});
client.login('your-token-goes-here');Si vous ne savez pas comment fonctionne l'exécution asynchrone de Node.js, vous essayeriez probablement quelque chose comme ceci :
client.on(Events.InteractionCreate, (interaction) => {
// ...
if (commandName === 'react') {
const response = interaction.reply({ content: 'Reacting!', withResponse: true });
const { message } = response.resource;
message.react('🇦');
message.react('🇧');
message.react('🇨');
}
});Mais comme tous ces méthodes sont démarrées en même temps, ce serait juste une course pour voir quelle requête au serveur se termine en premier, donc il n'y aurait aucune garantie qu'il réagisse du tout (si le message n'est pas récupéré) ou dans l'ordre que vous vouliez. Pour s'assurer qu'il réagit après l'envoi du message et dans l'ordre (a, b, c), vous devriez utiliser le rappel .then() des Promises que ces méthodes retournent. Le code ressemblerait à ceci :
client.on(Events.InteractionCreate, (interaction) => {
// ...
if (commandName === 'react') {
interaction.reply({ content: 'Reacting!', withResponse: true }).then((response) => {
const { message } = response.resource;
message.react('🇦');
message.react('🇧');
message.react('🇨');
message
.react('🇦')
.then(() => message.react('🇧'))
.then(() => message.react('🇨'))
.catch((error) => {
// gérer l'échec de tout rejet de Promise à l'intérieur
});
});
}
});Dans ce morceau de code, les Promises sont résolues en chaîne les unes avec les autres, et si l'une des Promises est rejetée, la fonction passée à .catch() est appelée. Voici le même code mais avec async/await :
client.on(Events.InteractionCreate, async (interaction) => {
// ...
if (commandName === 'react') {
const response = await interaction.reply({ content: 'Reacting!', withResponse: true });
const { message } = response.resource;
message.react('🇦');
message.react('🇧');
message.react('🇨');
await message.react('🇦');
await message.react('🇧');
await message.react('🇨');
}
});C'est principalement le même code, mais comment captureriez-vous maintenant les rejets de Promise puisque .catch() n'est plus là ? C'est aussi une caractéristique utile avec async/await ; l'erreur sera levée si vous l'attendez afin que vous puissiez envelopper les Promises attendues dans un try/catch, et c'est bon.
client.on(Events.InteractionCreate, async (interaction) => {
if (commandName === 'react') {
try {
const response = await interaction.reply({ content: 'Reacting!', withResponse: true });
const { message } = response.resource;
await message.react('🇦');
await message.react('🇧');
await message.react('🇨');
} catch (error) {
// gérer l'échec de tout rejet de Promise à l'intérieur
}
}
});Ce code semble propre et est également facile à lire.
Vous vous posez peut-être la question : "Comment obtendrais-je la valeur avec laquelle la Promise s'est résolue ?".
Examinons un exemple où vous voulez supprimer une réponse envoyée.
client.on(Events.InteractionCreate, (interaction) => {
// ...
if (commandName === 'react') {
interaction
.reply({ content: 'This message will be deleted.', withResponse: true })
.then((response) => setTimeout(() => response.resource.message.delete(), 10_000))
.catch((error) => {
// gérer l'erreur
});
}
});La valeur de retour d'un .reply() avec l'option withResponse définie sur true est une promesse qui se résout avec InteractionCallbackResponse, mais comment le même code avec async/await ressemblerait-il ?
client.on(Events.InteractionCreate, async (interaction) => {
if (commandName === 'delete') {
try {
const response = await interaction.reply({ content: 'This message will be deleted.', withResponse: true });
setTimeout(() => response.resource.message.delete(), 10_000);
} catch (error) {
// gérer l'erreur
}
}
});Avec async/await, vous pouvez assigner la fonction attendue à une variable représentant la valeur retournée. Maintenant vous savez comment vous utilisez async/await.