Promise
Uma Promise (promessa) em JavaScript é um objeto que representa o resultado futuro de uma operação assíncrona. No momento em que ela é criada, esse resultado ainda pode não existir. A Promise guarda o lugar desse valor e avisa o programa quando ele ficar disponível, ou quando a operação falhar.
Uma analogia: imagine que você compra um livro em uma loja online. Assim que confirma o pedido, a loja entrega um recibo. O livro ainda não está na sua mão: o recibo é a garantia de que a encomenda está a caminho. Mais tarde, duas coisas podem acontecer. O pacote chega e você abre a caixa, ou a loja cancela o pedido e você precisa reagir a isso (pedir reembolso, comprar em outro lugar). Enquanto espera, você não fica parado na porta: continua estudando, respondendo mensagens, fazendo outras tarefas.
A Promise é esse recibo. Ela nasce na hora em que a operação começa. O valor (o “livro”) chega depois. O restante do programa segue em frente e só trata o resultado quando a Promise muda de estado.
Antes das Promises, o JavaScript encadeava essas esperas com funções de callback passadas umas dentro das outras. Com várias etapas, o código ficava aninhado e difícil de ler. A Promise organiza essa espera em estados e em uma sequência de tratamentos.
Os três estados
Uma Promise está sempre em um destes estados:
- pending (pendente): estado inicial. A encomenda foi registrada, mas o pacote ainda não chegou e o pedido ainda não foi cancelado.
- fulfilled (cumprida): a operação terminou com sucesso. O pacote foi entregue e a Promise carrega o valor produzido.
- rejected (rejeitada): a operação falhou. O pedido foi cancelado e a Promise carrega o motivo da falha.
Quando a Promise sai de pending, ela se estabelece: passa para fulfilled ou para rejected e não volta atrás. Um pacote entregue não volta a estar “a caminho”.
As funções resolve e reject são o que a operação usa para concluir a Promise. Chamar resolve(valor) leva a Promise ao estado fulfilled e entrega o valor. Chamar reject(motivo) leva a Promise ao estado rejected e entrega o motivo da falha. O estado de sucesso tem o nome fulfilled. Em muitos textos e mensagens de erro aparece a palavra resolved como forma informal de dizer que a Promise já se estabeleceu com sucesso.
flowchart TD
pedido[Pedido confirmado] --> pending[pending: recibo na mao]
pending --> espera[O programa segue outras tarefas]
espera --> resultado{A operacao termina}
resultado -->|Sucesso| fulfilled[fulfilled]
resultado -->|Falha| rejected[rejected]
fulfilled --> thenFn[".then trata o valor"]
rejected --> catchFn[".catch trata o motivo"]
Criando uma Promise
Ao criar uma Promise com new Promise, você passa uma função executora. O JavaScript chama essa função na hora e entrega dois parâmetros: resolve e reject. Dentro dela fica o trabalho que pode demorar. No exemplo abaixo, o setTimeout simula os quatro segundos que a transportadora leva para concluir a entrega.
let entrega = new Promise((resolve, reject) => {
setTimeout(() => {
let estoqueDisponivel = true;
if (estoqueDisponivel) {
resolve("Livro entregue: JavaScript Guia Definitivo");
} else {
reject("Pedido cancelado: livro sem estoque");
}
}, 4000);
});
Trocar estoqueDisponivel para false simula a loja cancelando o pedido. A Promise é criada na mesma hora, em estado pending. Só quatro segundos depois ela é cumprida ou rejeitada.
Quando o pacote chega: .then() e .catch()
O método .then() registra o que fazer quando a Promise é cumprida: abrir a caixa e usar o valor. O método .catch() registra o que fazer quando ela é rejeitada: ler o motivo e reagir à falha.
entrega
.then((pacote) => {
console.log(pacote);
})
.catch((motivo) => {
console.error(motivo);
});
Com estoqueDisponivel igual a true, o console mostra a mensagem de entrega. Com false, o .catch() recebe "Pedido cancelado: livro sem estoque". O código depois de entrega.then(...) não fica parado nesses quatro segundos: o navegador continua livre para desenhar a página e responder ao usuário.
Encadeamento
Uma encomenda real tem etapas. O pagamento é confirmado, o pacote é separado no estoque e só então a transportadora faz a entrega. Cada etapa depende do resultado da anterior.
O encadeamento faz isso com vários .then(). O valor devolvido por um .then() vira a entrada do próximo. Se esse retorno for outra Promise, o próximo .then() espera essa nova Promise terminar.
function confirmarPagamento() {
return new Promise((resolve) => {
setTimeout(() => resolve("Pagamento confirmado"), 1000);
});
}
function separarPacote(etapaAnterior) {
return new Promise((resolve) => {
setTimeout(() => resolve(etapaAnterior + " → pacote separado"), 1000);
});
}
function enviar(etapaAnterior) {
return new Promise((resolve) => {
setTimeout(() => resolve(etapaAnterior + " → pedido a caminho"), 1000);
});
}
confirmarPagamento()
.then((resultado) => separarPacote(resultado))
.then((resultado) => enviar(resultado))
.then((resultado) => {
console.log(resultado);
})
.catch((motivo) => {
console.error(motivo);
});
Um único .catch() no final trata a falha de qualquer etapa da cadeia. Se o pagamento falhar, as etapas seguintes não rodam, do mesmo modo que a loja não separa um pacote de um pedido que não foi pago.
Async/Await
O ECMAScript 2017 introduziu async e await para escrever essa mesma espera em uma sequência linear, parecida com código que executa passo a passo.
Uma função marcada com async devolve uma Promise. Dentro dela, await pausa só aquela função até a Promise ser cumprida e entrega o valor direto numa variável. O restante da página continua rodando.
async function acompanharEntrega() {
try {
let pagamento = await confirmarPagamento();
let pacote = await separarPacote(pagamento);
let envio = await enviar(pacote);
console.log(envio);
} catch (motivo) {
console.error(motivo);
}
}
acompanharEntrega();
await só pode aparecer dentro de uma função async. O try/catch ocupa o lugar do .catch(): se alguma das Promises for rejeitada, a execução salta para o catch com o motivo da falha.
O fetch por dentro: XMLHttpRequest e Promise
O fetch() do navegador já devolve uma Promise. Pedir dados a um servidor é a mesma espera da encomenda: a requisição sai na hora, a resposta chega depois, e .then() (ou await) trata o que veio.
Por baixo, o navegador continua usando o XMLHttpRequest que você já viu em Ajax. Dá para escrever uma versão mínima do fetch e ver a Promise nascer em volta dessa requisição. A função abaixo devolve o recibo na hora. O XMLHttpRequest faz o trabalho e, quando a resposta chega, chama resolve ou reject.
function buscar(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.responseText);
} else {
reject(new Error("Erro HTTP: " + xhr.status));
}
}
};
xhr.onerror = () => {
reject(new Error("Falha de rede"));
};
xhr.send();
});
}
buscar("pedido.json")
.then((texto) => {
let pedido = JSON.parse(texto);
console.log(pedido.produto);
})
.catch((motivo) => {
console.error(motivo);
});
A chamada buscar("pedido.json") devolve a Promise ainda pendente, e o xhr.send() dispara a requisição sem travar a página. Quando readyState chega a 4, a operação terminou. Um status entre 200 e 299 cumpre a Promise com o texto da resposta. Qualquer outro status, ou uma falha de rede, rejeita a Promise e cai no .catch().
O fetch do navegador faz esse embrulho para você e cumpre a Promise com um objeto Response, em vez do texto cru. response.json() lê esse corpo e devolve outra Promise, já convertida em objeto. Por isso uma busca com fetch costuma ter dois .then(): o primeiro confirma a resposta e devolve o JSON; o segundo usa o objeto.
fetch("pedido.json")
.then((response) => {
if (!response.ok) {
throw new Error("Erro HTTP: " + response.status);
}
return response.json();
})
.then((pedido) => {
console.log(pedido.produto);
})
.catch((motivo) => {
console.error(motivo);
});
No fetch, um status 404 ou 500 ainda cumpre a Promise. O objeto Response chega no primeiro .then() com response.ok valendo false. A verificação do status fica ali: o throw desvia a cadeia para o .catch(), do mesmo modo que o reject faz na função buscar.
Neste diretório de exemplos há um Ajax escrito com Promise e versões menores com Promise e com async/await.
git clone -b dev https://github.com/rodrigoprestesmachado/cpw2
cd cpw2/exemplos/ajax-promise
code .
Exercícios do freeCodeCamp
Exercícios de Fixação Práticos
Os exercícios abaixo são progressivos. Cada um acrescenta uma peça em cima do anterior. Crie uma página HTML simples, coloque o script antes do </body> e acompanhe o resultado no console do navegador (ou em um elemento da página, quando o enunciado pedir).
-
O recibo da entrega
Crie uma Promise que, depois de 2 segundos, chame
resolvecom a mensagem"Encomenda entregue". Use.then()para mostrar essa mensagem no console. Abra a página e confira que a mensagem só aparece depois da espera. -
Pedido cancelado
Acrescente um caminho de falha à Promise do exercício 1. Use uma variável
estoqueDisponivel. Quando ela fortrue, resolva com a mensagem de entrega. Quando forfalse, rejeite com"Pedido cancelado: sem estoque". Trate a falha com.catch()e mostre o motivo no console. Teste os dois valores da variável. -
Duas etapas da encomenda
Escreva duas funções, cada uma devolvendo uma Promise que resolve depois de cerca de 1 segundo:
confirmarPagamento()resolve com"Pagamento confirmado".separarPacote(etapaAnterior)resolve com o texto da etapa anterior seguido de" → pacote separado".
Encadeie as duas com
.then(). No último.then(), mostre o texto final no console. Inclua um.catch()no fim da cadeia. -
A mesma encomenda com
async/awaitReescreva o exercício 3 dentro de uma função
async. Useawaitem cada etapa etry/catchpara o caso de falha. O texto exibido no console deve ser o mesmo do exercício anterior. -
Uma função, dois destinos
Escreva uma função
consultarPedido(numero)que devolve uma Promise. Senumerofor maior que zero, resolva com"Pedido " + numero + " encontrado". Caso contrário, rejeite com"Número de pedido inválido". Chame a função duas vezes, uma com um número válido e outra com0, e trate sucesso e falha em cada chamada. As duas chamadas podem usar.then()/.catch()ouasync/await. -
Buscar um JSON com
fetch()Crie um arquivo
pedido.jsonna mesma pasta da página, por exemplo{ "produto": "Caderno", "status": "a caminho" }. Usefetch("pedido.json"). A funçãofetchdevolve uma Promise: no primeiro.then(), converta a resposta comresponse.json()e devolva essa nova Promise. No.then()seguinte, mostre apenas oprodutodentro de um<p id="resultado">. Trate falha de rede com.catch(). Sirva a pasta com um servidor local para ofetchconseguir ler o arquivo. -
Um
fetchmínimo comXMLHttpRequestEscreva uma função
buscar(url)que devolva uma Promise. Dentro dela, useXMLHttpRequestpara fazer um GET assíncrono a essa URL. QuandoreadyStatefor 4 e ostatusestiver entre 200 e 299, chameresolvecomxhr.responseText. Se o status estiver fora dessa faixa, ou se ocorrer um erro de rede (xhr.onerror), chamereject. Use opedido.jsondo exercício anterior: no.then(), converta o texto comJSON.parsee mostre oprodutodentro de<p id="resultado">.
Exercício de Fixação Teórico
Referências
-
Promise no MDN Web Docs
-
Usando promises no MDN Web Docs
-
Async/Await no JavaScript.info

CC BY 4.0 DEED