Promises
Esta aula aborda o conceito de Promises em JavaScript, explicando seus estados, métodos then/catch/finally, encadeamento e como criar uma Promise. O conteúdo inclui exemplos práticos e boas práticas para lidar com operações assíncronas.
Promises são objetos que representam a eventual conclusão (ou falha) de uma operação assíncrona e seu valor resultante. Elas são uma forma mais limpa e flexível de lidar com código assíncrono do que callbacks aninhados, evitando o famoso 'callback hell'. Uma Promise pode estar em um de três estados: pendente, resolvida ou rejeitada, e fornece métodos para reagir a essas mudanças de estado.
Nesta aula, vamos explorar como criar Promises, como consumi-las usando then, catch e finally, e como encadear múltiplas operações assíncronas de forma sequencial. Ao final, você será capaz de escrever código assíncrono mais legível e robusto.
Estados
Uma Promise possui três estados possíveis: pending (pendente), fulfilled (resolvida) e rejected (rejeitada). Quando a Promise é criada, ela inicia no estado pendente. Se a operação assíncrona for bem-sucedida, a Promise é resolvida (fulfilled) com um valor. Se ocorrer um erro, ela é rejeitada (rejected) com um motivo (geralmente um erro). Uma vez que a Promise é resolvida ou rejeitada, ela não pode mudar de estado novamente.
O estado de uma Promise é interno e não pode ser acessado diretamente pelo código. Em vez disso, usamos os métodos then, catch e finally para reagir às mudanças de estado. Exemplo conceitual:
const promise = new Promise((resolve, reject) => {
// Operação assíncrona
if (sucesso) {
resolve('Valor de sucesso');
} else {
reject(new Error('Algo deu errado'));
}
});
// Estado inicial: pending
// Se resolve for chamado: fulfilled
// Se reject for chamado: rejectedthen/catch/finally
O método then é usado para registrar callbacks que serão executados quando a Promise for resolvida (fulfilled) ou rejeitada (rejected). Ele aceita até dois argumentos: uma função de sucesso e uma função de erro. No entanto, é mais comum usar catch para tratar erros separadamente, melhorando a legibilidade. O método finally é executado independentemente do resultado, sendo útil para limpeza.
Exemplo com then/catch/finally:
const minhaPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const numero = Math.random();
if (numero > 0.5) {
resolve('Sucesso! Número: ' + numero);
} else {
reject(new Error('Falha! Número: ' + numero));
}
}, 1000);
});
minhaPromise
.then(resultado => {
console.log(resultado);
})
.catch(erro => {
console.error(erro.message);
})
.finally(() => {
console.log('Operação finalizada');
});O finally é útil para esconder indicadores de carregamento ou liberar recursos, independentemente do sucesso ou falha.
Encadeamento
O encadeamento (chaining) de Promises permite executar operações assíncronas em sequência, onde cada then retorna uma nova Promise. Isso evita o aninhamento excessivo e torna o código mais linear. O valor retornado por um then é passado para o próximo then na cadeia. Se uma Promise for rejeitada em qualquer ponto, o fluxo pula para o próximo catch.
Exemplo de encadeamento:
function asyncOperation(valor) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(valor * 2);
}, 500);
});
}
asyncOperation(5)
.then(resultado1 => {
console.log('Primeiro resultado:', resultado1);
return asyncOperation(resultado1);
})
.then(resultado2 => {
console.log('Segundo resultado:', resultado2);
return asyncOperation(resultado2);
})
.then(resultado3 => {
console.log('Terceiro resultado:', resultado3);
})
.catch(erro => {
console.error('Erro:', erro);
});
// Saída (após 1.5s):
// Primeiro resultado: 10
// Segundo resultado: 20
// Terceiro resultado: 40Note que se qualquer Promise na cadeia for rejeitada, o catch será chamado imediatamente, ignorando os then subsequentes.
Criando uma Promise
Para criar uma Promise, usamos o construtor new Promise(executor), onde executor é uma função que recebe dois parâmetros: resolve e reject. Dentro do executor, colocamos a operação assíncrona. Quando a operação termina, chamamos resolve com o resultado ou reject com o erro. O executor é executado imediatamente quando a Promise é criada.
Exemplo prático: simular uma requisição HTTP com setTimeout.
function buscarDados(url) {
return new Promise((resolve, reject) => {
// Simula requisição assíncrona
setTimeout(() => {
if (url === 'https://api.exemplo.com/dados') {
resolve({ id: 1, nome: 'João' });
} else {
reject(new Error('URL inválida'));
}
}, 2000);
});
}
buscarDados('https://api.exemplo.com/dados')
.then(dados => console.log(dados))
.catch(erro => console.error(erro));
// Após 2s: { id: 1, nome: 'João' }É importante lembrar que o executor não deve ser uma função async (a menos que você saiba o que está fazendo), pois o retorno de uma Promise async pode causar comportamentos inesperados. Prefira usar o construtor diretamente.
Boas Práticas
Sempre trate rejeições com catch para evitar erros não capturados. Evite o uso de throw dentro de then sem um catch apropriado. Prefira encadeamento a aninhamento. Use Promise.all para paralelismo quando a ordem não importa. Lembre-se de que Promises são assíncronas, mesmo que resolvidas imediatamente: o callback de then sempre será chamado em um microtask.
Referências
- MDN: Promise
- JavaScript.info: Promises
- W3Schools: JavaScript Promises
- Google Developers: Promises
- Node.js: Understanding Promises
Exercícios
- Crie uma Promise que resolve após 2 segundos com a string "Pronto!".
- Use .catch() para tratar uma Promise que rejeita com um erro.
- Encadeie duas Promises: a primeira retorna 10, a segunda multiplica por 2.
- Crie uma função que retorna uma Promise que rejeita se o argumento for negativo.
- Use finally para logar "Fim" após uma Promise que resolve.
const promise = new Promise((resolve) => {
setTimeout(() => resolve('Pronto!'), 2000);
});
promise.then(console.log);const promise = new Promise((_, reject) => {
reject(new Error('Falha'));
});
promise.catch(erro => console.error(erro.message));function asyncDouble(n) {
return new Promise(resolve => setTimeout(() => resolve(n * 2), 500));
}
Promise.resolve(10)
.then(asyncDouble)
.then(console.log); // 20function checkPositive(numero) {
return new Promise((resolve, reject) => {
if (numero >= 0) {
resolve('Número positivo');
} else {
reject(new Error('Número negativo'));
}
});
}
checkPositive(-1).catch(console.error);Promise.resolve('OK')
.then(console.log)
.finally(() => console.log('Fim'));