Uma Promise é um objeto que representa o resultado eventual de uma operação assíncrona, como uma chamada de API, permitindo lidar com resultados e erros de forma mais organizada do que os antigos callbacks. Usando métodos como .then() para sucesso e .catch() para falhas, ele funciona como um "contrato" de que um valor estará disponível agora, daqui a pouco ou nunca.
Estados de uma Promise
Uma Promise pode estar em apenas um dos três estados abaixo:
-
pending- ainda está em execução -
fulfilled- foi concluída com sucesso -
rejected- foi concluída com erro
Esses estados são imutáveis: uma vez fulfilled ou rejected, a Promise não voltará para pending
Por que Promises existem?
Para resolver o problema do callback hell e trazer um modelo mais previsível, encadeável e configável para operações assíncronas.
Com Promises conseguimos:
- Evitamos pirâmides de callback
- Tratamos erros de forma unificada
- Encadeamos operações de forma mais clara
- Temos integrações perfeitas com
async/await
Criando uma Promise:
const promise = new Promise((resolve, reject) => {
const sucesso = true
if (sucesso) {
resolve("Working!")
} else {
reject("Something wrong!")
}
})
A função passada para o construtor recebe dois parâmetros:
-
resolve(value)=> finaliza com sucesso. -
reject(value)=> finaliza com erro (o reject não espera um erro mas é uma boa prática rejeitar com um objeto Error para facilitar o debugging, stack trace e identificar falhas --reject(new Error('error))).
Consumindo Promises:
getUser()
.then(user => getOrder(user.id))
.then(orders => sendEmail(orders))
.catch(error => console.log(error))
Cada .then() adiciona uma nova microtask no Event Loop (explicado mais afundo durante o estudo)
Outro exemplo de encadeamento:
Promise.resolve(2)
.then(num => num * 2)
.then(num => num + 3)
.then(num => console.log(num)) // 7
Uso do .finally()
Executa sempre, independemente de sucesso ou erro
fetch("/api")
.then(res => console.log("sucesso"))
.catch(err => console.error("erro"))
.finally(() => console.log("sempre executa"));
Relação das Promises com o Event Loop
Como já sabemos (se chegou até aqui e ainda não tiver noção básica de como o Event Loop funciona, recomendo fortemente que volte e entenda um pouco sobre antes de continuar), o Event Loop é o coração da execução assíncrona no JavaScript.
Ele controla a ordem em que o código é executado e coordena todas as operações assíncronas -- inclusive Promises, é essencial entender como elas interagem entre sí.
O JavaScript é single-threaded, o ambiente não
O motor JavaScript roda em uma única thread, porém o ambiente (Node.js ou navegador) possui várias APIs assíncronas (Web APIs e libuv) que operam fora dessa thread e devolvem o resultado via filas gerenciadas pelo Event Loop.
Promises utilizam a microtask queue, que tem prioridade sobre callbacks de setTimeout por exemplo, que ficam na macrotask queue
O ciclo do Event Loop segue esta ordem:
- Executa todo o código síncrono
- Executa todas as microtasks pendentes
- Executa uma macrotask (se não tiver mais microtasks pendentes)
- Repete o ciclo
Por isso que Promises são mais rápidas que timeouts:
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('microtask'))
// microtask
// timeout
Mas quando uma Promise é resolvida, elas são tratadas como callbacks?
Conceitualmente, sim mas uma diferença fundamental
Quando uma Promise é resolvida ou rejeitada, o callback associado a ela não executa imediatamente, ele é agendado na microtask queue, como dito anteriormente.
Promise.resolve(10).then(x => console.log(x));
A execução inicial:
- a chamada
Promise.resolve(10)é executada na Call Stack - a Promise é criada e já nasce no estado
fulfilled - a chamada
.then()também é executada na Call Stack
O .then():
- não executa o callback
- apenas registra um Promise Reaction Record
- esse reaction é armazenado internamente pelo motor JS
- nada é enfileirado ainda
Quando a Promise é fulfilled ou rejected:
- o motor JS percorre os Promise Reaction Records registrados
- para cada reaction, cria um Promise Reaction Job
- esse job contém o callback registrado no then/catch/finally
- o motor JS agenda internamente esse job como uma microtask
- a microtask é colocada na microtask queue
Ou seja, o que realmente acontece é que o motor JavaScript percorre a lista de Promise Reaction Records e, para cada um deles, cria um PromiseReactionJob. Esse job é então agendado na microtask queue. É dentro desse reaction job que o callback registrado no then, catch ou finally é efetivamente executado.
Modelo mental do que acontece, para ter uma visualização do fluxo:
.then()
↓
cria um Promise Reaction Record (armazena o callback)
↓
quando a Promise resolve/reject
↓
o motor JS cria um PromiseReactionJob para cada Promise Reaction Record registrado
↓
job onde o callback está registrado (then/catch/finally)
↓
agenda o job na Microtask Queue
↓
executa na Call Stack
O ponto que muda tudo
No caso das Promises, todo o fluxo acontece inteiramente dentro do motor JavaScript. Não há envolvimento de Web APIs, libuv ou eventos externos. O agendamento ocorre por meio do job scheduling interno do próprio engine.
Por isso, no funcionamento do Event Loop, os callbacks enfileirados como microtasks sempre têm prioridade sobre os enfileirados como macrotasks.
Como as Promises são resolvidas internamente pelo motor JavaScript, seus reaction jobs são agendados como microtasks e, por esse motivo, são sempre executados antes de qualquer macrotask.
Conclusão
Ao longo deste estudo, vimos que Promises vão muito além de uma simples alternativa a callbacks. Elas introduzem um modelo interno de execução assíncrona que é tratado diretamente pelo motor JavaScript, sem depender de ambiente externo.
Entender que:
.then(), .catch() e .finally() não executam callbacks imediatamente, mas registram Promise Reaction Records, que posteriormente geram PromiseReactionJobs nos quais são agendados como microtasks, é o ponto que realmente muda a forma como enxergamos o Event Loop.
Justamente esse mecanismo interno que explica por que Promises são previsíveis, se encadeiam de forma segura e sempre têm prioridade sobre macrotasks, como setTimeout, mesmo com delay zero.
Quando conseguimos entender esse fluxo, muitos "comportamentos estranhos" que nos deparamos durante o dia a dia no desenvolvendo em JavaScript ou TypeScript, deixam de ser mágicos e passam a fazer mais sentido. Promises não são mais uma caixa-preta -- elas são apenas jobs bem organizados, executados no momento certo
Se você quiser avançar ainda mais, o próximo passo natural seria entender como podemos realizar tratativas de erros, como os métodos especiais como Promise.all, Promise.allSettled e Promise.race funcionam e também como o async/await se apoia exatamente nesse mesmo mecanismo, apenas oferecendo uma sintaxe mais legível sobre as Promises que já conhecemos.
Top comments (0)