Promises & Async
Programação Assíncrona Moderna
JavaScript Moderno: Promises e Async/Await
A programação assíncrona está no coração do JavaScript moderno. As Promises e o async/await são ferramentas essenciais para gerenciar operações assíncronas de forma elegante e fácil de manter.
As Promises
Uma Promise é um objeto que representa a eventual conclusão (ou falha) de uma operação assíncrona.
Criação de uma Promise
const maPromesse = new Promise((resolve, reject) => {
// Opération asynchrone
setTimeout(() => {
const succes = true;
if (succes) {
resolve("Opération réussie !");
} else {
reject(new Error("L'opération a échoué"));
}
}, 1000);
});
// Utilisation
maPromesse
.then(resultat => console.log(resultat))
.catch(erreur => console.error(erreur));
Estados de uma Promise
Uma Promise pode estar em um dos três estados a seguir:
- Pending: estado inicial, nem cumprida nem rejeitada
- Fulfilled: operação bem-sucedida
- Rejected: operação com falha
const promessePending = new Promise(() => {});
console.log(promessePending); // Promise { <pending> }
const promesseFulfilled = Promise.resolve("Succès");
console.log(promesseFulfilled); // Promise { <fulfilled>: "Succès" }
const promesseRejected = Promise.reject("Erreur");
console.log(promesseRejected); // Promise { <rejected>: "Erreur" }
Encadeamento de Promises
As Promises podem ser encadeadas para executar operações sequenciais:
function obtenirUtilisateur(id) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id, nom: "Alice" });
}, 1000);
});
}
function obtenirCommandes(utilisateur) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
utilisateur,
commandes: [
{ id: 1, produit: "Livre" },
{ id: 2, produit: "Ordinateur" }
]
});
}, 1000);
});
}
// Chaînage
obtenirUtilisateur(1)
.then(utilisateur => obtenirCommandes(utilisateur))
.then(resultat => console.log(resultat))
.catch(erreur => console.error(erreur));
Métodos úteis com Promises
Promise.all()
Aguarda até que todas as promises sejam resolvidas:
const promise1 = Promise.resolve(3);
const promise2 = new Promise(resolve => setTimeout(() => resolve(42), 1000));
const promise3 = fetch('https://api.example.com/data');
Promise.all([promise1, promise2, promise3])
.then(resultats => {
console.log(resultats); // [3, 42, Response]
})
.catch(erreur => {
console.error("Une des promises a échoué:", erreur);
});
Promise.race()
Retorna a primeira promise resolvida ou rejeitada:
const promise1 = new Promise(resolve => setTimeout(() => resolve("un"), 1000));
const promise2 = new Promise(resolve => setTimeout(() => resolve("deux"), 500));
Promise.race([promise1, promise2])
.then(resultat => console.log(resultat)); // "deux"
Promise.allSettled()
Aguarda até que todas as promises sejam concluídas (resolvidas ou rejeitadas):
const promises = [
Promise.resolve(1),
Promise.reject("erreur"),
Promise.resolve(3)
];
Promise.allSettled(promises)
.then(resultats => {
resultats.forEach(resultat => {
if (resultat.status === "fulfilled") {
console.log("Succès:", resultat.value);
} else {
console.log("Échec:", resultat.reason);
}
});
});
Async/Await
async/await é uma sintaxe mais elegante para trabalhar com Promises.
Funções assíncronas
async function obtenirDonnees() {
try {
const utilisateur = await obtenirUtilisateur(1);
const commandes = await obtenirCommandes(utilisateur);
return commandes;
} catch (erreur) {
console.error("Erreur:", erreur);
throw erreur;
}
}
// Utilisation
obtenirDonnees()
.then(resultat => console.log(resultat))
.catch(erreur => console.error(erreur));
Com arrow functions
const obtenirDonnees = async () => {
const utilisateur = await obtenirUtilisateur(1);
const commandes = await obtenirCommandes(utilisateur);
return commandes;
};
Paralelização com async/await
async function obtenirTout() {
try {
// Exécution parallèle
const [utilisateurs, produits] = await Promise.all([
obtenirUtilisateurs(),
obtenirProduits()
]);
return { utilisateurs, produits };
} catch (erreur) {
console.error("Erreur:", erreur);
throw erreur;
}
}
Loops assíncronos
async function traiterUtilisateurs(utilisateurs) {
// Séquentiel
for (const utilisateur of utilisateurs) {
await envoyerEmail(utilisateur);
}
// Parallèle
const promesses = utilisateurs.map(utilisateur => envoyerEmail(utilisateur));
await Promise.all(promesses);
}
Padrões e boas práticas
Padrão de retry
async function avecRetry(fn, maxTentatives = 3) {
let dernierErreur;
for (let tentative = 1; tentative <= maxTentatives; tentative++) {
try {
return await fn();
} catch (erreur) {
dernierErreur = erreur;
console.log(`Tentative ${tentative} échouée`);
if (tentative < maxTentatives) {
await new Promise(resolve =>
setTimeout(resolve, 1000 * tentative)
);
}
}
}
throw dernierErreur;
}
// Utilisation
const fetchAvecRetry = () =>
avecRetry(() => fetch('https://api.example.com/data'));
Timeout para Promises
function avecTimeout(promesse, delai) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Timeout')), delai);
});
return Promise.race([promesse, timeoutPromise]);
}
// Utilisation
avecTimeout(fetch('https://api.example.com/data'), 5000)
.then(response => response.json())
.catch(erreur => console.error('Erreur ou timeout:', erreur));
Tratamento de erros
async function gererErreurs() {
try {
const donnees = await fetch('https://api.example.com/data');
if (!donnees.ok) {
throw new Error(`HTTP error! status: ${donnees.status}`);
}
const resultat = await donnees.json();
return resultat;
} catch (erreur) {
if (erreur instanceof TypeError) {
console.error('Erreur réseau:', erreur);
} else if (erreur.name === 'AbortError') {
console.error('Requête annulée:', erreur);
} else {
console.error('Autre erreur:', erreur);
}
throw erreur;
}
}
Casos de uso práticos
Requisições HTTP com fetch
async function obtenirDonneesAPI() {
try {
const response = await fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ key: 'value' })
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Erreur:', error);
throw error;
}
}
Carregamento de imagens
function chargerImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error(`Échec du chargement de ${url}`));
img.src = url;
});
}
async function chargerGalerie(urls) {
try {
const images = await Promise.all(
urls.map(url => chargerImage(url))
);
return images;
} catch (erreur) {
console.error('Erreur de chargement:', erreur);
throw erreur;
}
}
Cache com Promises
class CachePromise {
constructor() {
this.cache = new Map();
}
async get(key, fetchFn) {
if (this.cache.has(key)) {
return this.cache.get(key);
}
const promise = fetchFn();
this.cache.set(key, promise);
try {
await promise;
return promise;
} catch (erreur) {
this.cache.delete(key);
throw erreur;
}
}
}
// Utilisation
const cache = new CachePromise();
async function obtenirUtilisateur(id) {
return cache.get(`utilisateur-${id}`, async () => {
const response = await fetch(`/api/utilisateurs/${id}`);
return response.json();
});
}
Exercícios práticos
- Simulação de uma fila de espera
class FileAttente {
constructor() {
this.taches = [];
}
ajouterTache(tache) {
return new Promise((resolve, reject) => {
this.taches.push({ tache, resolve, reject });
if (this.taches.length === 1) {
this.executerSuivant();
}
});
}
async executerSuivant() {
if (this.taches.length === 0) return;
const { tache, resolve, reject } = this.taches[0];
try {
const resultat = await tache();
resolve(resultat);
} catch (erreur) {
reject(erreur);
} finally {
this.taches.shift();
this.executerSuivant();
}
}
}
// Utilisation
const file = new FileAttente();
file.ajouterTache(() => new Promise(resolve =>
setTimeout(() => resolve("Tâche 1"), 1000)
));
file.ajouterTache(() => new Promise(resolve =>
setTimeout(() => resolve("Tâche 2"), 500)
));
- Gerenciador de requisições concorrentes
class GestionnaireRequetes {
constructor(maxConcurrent = 3) {
this.maxConcurrent = maxConcurrent;
this.actives = 0;
this.file = [];
}
async ajouter(fn) {
if (this.actives >= this.maxConcurrent) {
await new Promise(resolve => this.file.push(resolve));
}
this.actives++;
try {
return await fn();
} finally {
this.actives--;
if (this.file.length > 0) {
const suivant = this.file.shift();
suivant();
}
}
}
}
// Utilisation
const gestionnaire = new GestionnaireRequetes(2);
async function effectuerRequetes() {
const urls = ['url1', 'url2', 'url3', 'url4', 'url5'];
const promesses = urls.map(url =>
gestionnaire.ajouter(() => fetch(url))
);
return Promise.all(promesses);
}
Conclusão
As Promises e o async/await são ferramentas fundamentais para lidar com a assincronicidade no JavaScript moderno:
- As Promises oferecem uma abordagem estruturada para gerenciar operações assíncronas
- O async/await simplifica a sintaxe e deixa o código mais legível
- Padrões como retry, timeout e cache são essenciais para aplicações robustas
- O tratamento adequado de erros é crucial para a confiabilidade das aplicações
No próximo tutorial, vamos explorar os módulos ES e como organizar seu código JavaScript de forma eficiente.
Precisa revisar a programação orientada a objetos? Confira nosso tutorial anterior sobre POO.
Comentários
Os comentários são alimentados pelo GitHub Discussions
Conecte-se com o GitHub para participar da discussão