Promessas
As promessas (promises) são essenciais à programação assíncrona com JavaScript, portanto aqueles que desejarem ingressar numa carreira de "Front-end" precisam estudá-las com o máximo de afinco.
E o que que é "Programação Assíncrona"?
A programação assíncrona é um "setor" da programação que permite que diferentes partes do código sejam executadas em períodos diferentes de tempo, em vez de ser tudo imediato, como é o padrão.
Isso costuma ser necessário quando queremos obter uma informação de algum servidor remoto e integrar a resposta desse servidor à nossa própria aplicação:
function pegaStatusDoServidor() {
const resultado = fetch("/server/status");
// ISTO NÃO FUNCIONARÁ!
console.log("O status do servidor é ", resultado.ok);
}
Em várias linguagens de programação, como o Python, essa abordagem funcionaria, já que suas funções são síncronas por padrão.
Mas no JavaScript, várias APIs exigirão que uma função espere para poder fazer algo a fim de evitar sobrecarga dos recursos físicos, o que faz delas assíncronas por padrão. Resumindo, esse código não vai fazer o que a gente espera, pois já que a função "fetch" é assíncrona, ela retornará 'algo' que não é exatamente a resposta final, mas que eventualmente o será.
Pense numa API como a "porta de entrada" para contatar uma aplicação X e obter dados dela: elas têm documentação independente (além de, às vezes, exigirem autenticação por "token" e/ou pagamento a fim de fazer manutenções de infraestrutura para serem usadas).
Esse "algo" retornado da função "fetch" é o que se chama de "Promise" no JavaScript.
Para que o código mais acima funcionasse, devemos refatorar a função da seguinte forma:
function pegaStatusDoServidor() {
const resultado = fetch("/server/status");
resultado.then(function(status) {
console.log("O status do servidor é ", status.ok);
});
}
Perceba que, desta vez, usamos a função "then", que é um dos métodos característicos de um objeto do tipo "Promise".
O Objeto "Promise"
Promise é um objeto nativo do JavaScript dotado de duas características:
-
Ele recebe apenas um argumento, que é uma função. Essa função precisa ter dois parâmetros: uma função "
resolve" e uma função "reject". O código escrito dentro do corpo da promessa precisa usar ao menos uma dessas duas funções.- Para fins didáticos, referir-me-ei ao "fim" de uma promessa como "conclusão", mas quando uma promessa conclui, seu fim pode ser tanto positivo quanto negativo, como veremos adiante.
-
Ela pode ser aguardada usando o método "
then" (ou similares) ou a palavra reservada "await".Para as palavras reservadas "async" e "await", fizemos um guia em particular.
Em suma, chamamos de função assíncrona toda aquela cujo retorno, em vez de ser o valor que se espera no primeiro momento, será um objeto Promise que eventualmente se tornará esse exato valor.
Suponhamos que queremos calcular a soma de dois números, mas escrevendo uma função que retornará uma Promise em vez dessa soma.
function somaAssincrona(x, y) {
const p = new Promise((resolve, reject) => {
// isto conclui a promessa que recém-criamos retornando x + y
resolve(x + y);
});
// já isto retorna a própria promessa em vez do valor esperado
return p;
}
// agora, abriremos a promessa (como uma caixa de presentes)
somaAssincrona(5, 7).then((resultado) => {
console.log(`O resultado da soma é ${resultado}.`);
});
"Pra que isso serve"? É para quando o cálculo precisa acontecer indiretamente, como após esperar algum tempo ou após obter dados de um servidor remoto com a função "fetch".
Modifiquemos o exemplo para que a solução seja entregue após meio segundo:
function somaAssincrona(x, y) {
console.log("1. A função 'somaAssincrona' é executada.");
const p = new Promise((resolve, reject) => {
// isto rodará apenas daqui a 500 ms
setTimeout(() => {
console.log("4. Concluindo a promessa 'p' da função 'somaAssincrona' com o resultado após 500 ms de espera.");
resolve(x + y);
}, 500);
// retornos são desnecessários
console.log("2. A promessa 'p' da função 'somaAssincrona' é inicializada.");
});
console.log("3. A função 'somaAssincrona' prepara-se para retornar a promessa 'p' sem saber que fim ela vai ter.");
return p;
}
// agora, abriremos a promessa
somaAssincrona(5, 7).then((resultado) => {
console.log(`5. O resultado da adição é ${resultado}.`);
});
exibe, além das outras linhas, "5. O resultado da adição é 12."
Rejeitando Promessas
Em um fluxo síncrono, se quisermos informar ao usuário que algo deu errado e disparar um erro a ser exibido, podemos ativar uma exceção por meio da função "throw". Mas no contexto assíncrono das promessas, a gente tem que ativar a função "reject".
Digamos que precisamos reescrever a função acima, mas com a promessa guardando uma rejeição formal em caso de recebimento de valor negativo. Teríamos isto:
function somaAssincrona(x, y) {
return new Promise((resolve, reject) => {
// rode isto daqui a 500 ms
setTimeout(() => {
if (x < 0 || y < 0) {
reject("Erro: valores negativos não são permitidos.");
} else {
resolve(x + y);
}
}, 500);
// sem retornos aqui
});
}
somaAssincrona(-5, 7).then((resultado) => {
console.log(`O resultado da adição é ${resultado}.`);
}).catch((erro) => {
console.log(`Erro: ${erro}`);
});
exibe "O resultado da adição é 12."
Exercise
Escreva uma função que receba uma string como parâmetro e retorne um objeto do tipo Promise.
A promessa deve dar "resolve" retornando a versão da string toda em letras maiúsculas, mas dar "reject" no caso de a string ser nula.