Em JavaScript, uma Promise é um objeto que representa um valor que pode não estar disponível ainda, mas será resolvido no futuro. As Promises são usadas para lidar com operações assíncronas, como fazer requisições de rede ou acessar bancos de dados, onde o resultado não está imediatamente disponível.
Como funcionam as Promises?
Uma Promise é um proxy para um valor que pode não ser conhecido necessariamente quando a promise é criada. Ela permite que você associe manipuladores a um valor de sucesso eventual ou motivo de falha de uma ação assíncrona. Isso permite que métodos assíncronos retornem valores como métodos síncronos: em vez de retornar imediatamente o valor final, o método assíncrono retorna uma promise para fornecer o valor em algum momento futuro.
Uma Promise tem três estados possíveis:
- Pending: O estado inicial de uma Promise. A Promise não foi cumprida nem rejeitada.
- Fulfilled: A Promise foi resolvida e o valor resultante está disponível.
- Rejected: A Promise foi rejeitada e ocorreu um erro.
Uma vez que uma Promise é resolvida, ela não pode ser resolvida novamente. A função resolve() ou reject() só pode ser chamada uma vez, e quaisquer chamadas subsequentes a essas funções não terão efeito. A imutabilidade de uma Promise resolvida é uma característica importante porque garante que o valor da Promise permaneça consistente e previsível. Uma vez que uma Promise é resolvida, seu valor não pode ser alterado, o que ajuda a prevenir comportamentos inesperados.
Como criar Promises?
Uma Promise é criada usando o construtor Promise, que recebe um único argumento, uma função chamada executor. A função executor recebe dois argumentos: resolve e reject. Essas funções são chamadas quando a Promise é cumprida ou rejeitada.
Para mostrar o que quero dizer...
const getUsers = new Promise((resolve, reject) => { setTimeout(() => { const users = [ { name: "John Doe 01" }, { name: "John Doe 02" }, { name: "John Doe 03" } ]; resolve(users) }, 1000) })
A Promise no exemplo acima será resolvida após um segundo, e o valor da Promise resolvida será o array de usuários.
Uma vez que uma Promise é criada, você pode usar o método then para anexar uma função de retorno de chamada que será chamada quando a Promise for cumprida. O método then recebe dois argumentos: uma função de retorno de chamada para o valor cumprido e uma função de retorno de chamada para o valor rejeitado.
Para mostrar o que quero dizer...
getUsers.then((users) => { console.log(users); }).catch((error) => { console.error(error) })
Seguindo em frente na nossa aventura, vamos dar uma olhada em um exemplo de uma Promise que foi rejeitada.
Para mostrar o que quero dizer...
const getUsers = new Promise((resolve, reject) => { setTimeout(() => { const users = []; reject("Algo deu errado!"); }, 1000) }) getUsers.then((users) => { console.log(users); }).catch((error) => { console.error(error) })
Promises encadeadas
Os métodos abaixo são usados para associar uma ação adicional a uma Promise que se torna resolvida. Como esses métodos retornam Promises, eles podem ser encadeados.
Promise.prototype.then() Promise.prototype.catch() Promise.prototype.finally()
Encadear Promises em JavaScript envolve a criação de uma sequência de Promises que são executadas uma após a outra. Cada Promise na cadeia depende da conclusão bem-sucedida da Promise anterior, portanto, se qualquer Promise na cadeia falhar, toda a cadeia falhará.
Vamos ver como podemos encadear Promises em JavaScript:
const fetchData = (url) => { return fetch(url).then((response) => response.json()).then((data) => { return data }) } fetchData("https://jsonplaceholder.typicode.com/users").then((data) => { console.log("Array de usuários: ", data) return fetchData(`https://jsonplaceholder.typicode.com/posts?userId=${data[0].id}`); }).then((data) => { console.log("Array de posts do primeiro usuário: ", data) return fetchData( `https://jsonplaceholder.typicode.com/comments?postId=${data[0].id}` ); }).then((data) => { console.log("Array de comentários do primeiro post: ", data) }).catch((error) => { console.log(error) })
Aqui, a função fetchData() é usada para buscar dados de uma API remota e realizar alguma operação sobre eles. A função fetchData() retorna uma Promise que é resolvida com o resultado da operação.
A cadeia de Promises começa buscando os dados do usuário na API, em seguida, usando o ID do primeiro usuário para buscar seus posts e, finalmente, usando o ID do primeiro post para buscar os comentários para aquele post. Cada método then() na cadeia lida com o valor resolvido da Promise anterior e o método catch() final lida com quaisquer erros que ocorram durante a cadeia.
Podemos criar muitas cadeias com o método then() conforme as necessidades. Como código síncrono, o encadeamento resultará em uma sequência que é executada em série. Vamos ver um exemplo simples...
myPromise .then(() => "Tarefa #1") .then(() => "Tarefa #2") .then(() => "Tarefa #3") .then(() => "Tarefa #4") .catch((error) => { console.log(error) })
Benefícios das Promises
As Promises fornecem vários benefícios em relação às abordagens baseadas em callbacks tradicionais para lidar com operações assíncronas em JavaScript. Alguns dos principais benefícios incluem:
- Melhor legibilidade: Promises permitem que você escreva código que seja mais legível e fácil de entender do que abordagens baseadas em callbacks tradicionais. Com Promises, você pode encadear operações assíncronas em uma sequência, o que torna claro em qual ordem as operações são executadas.
- Melhoria no tratamento de erros: Promises tornam mais fácil o tratamento de erros que ocorrem durante operações assíncronas. Com Promises, você pode usar o método catch para tratar erros que ocorrem em qualquer etapa da cadeia, em vez de ter que lidar com erros separadamente para cada etapa.
- Evitando o callback hell: Promises podem ajudar a evitar o "callback hell", uma situação em que você tem uma cadeia de callbacks aninhados que podem se tornar difíceis de gerenciar e depurar. Com Promises, você pode encadear operações assíncronas sem precisar aninhar vários níveis de callbacks.
- Capacidade de retornar um valor: Promises permitem que você retorne um valor de uma operação assíncrona, o que torna mais fácil passar o resultado de uma operação para outra em uma sequência. Isso é particularmente útil quando você precisa executar várias operações assíncronas em uma sequência e precisa usar o resultado de cada operação na próxima operação.
- Melhor compatibilidade: Promises são um recurso padronizado no JavaScript moderno e são suportadas por todos os navegadores modernos e pelo Node.js. Isso significa que Promises podem ser usadas em diferentes ambientes sem exigir código diferente para cada ambiente.
Como cancelar uma Promise?
No JavaScript moderno - Não, você não pode cancelar uma Promise depois que ela foi criada. Ela executará seu código e resolverá ou rejeitará, e não há uma maneira integrada de cancelar a operação.
Existem algumas técnicas que você pode usar para simular o cancelamento:
- Timeout: Você pode usar um timeout para rejeitar a Promise se ela demorar muito para resolver. Essa técnica é útil se você estiver fazendo uma solicitação de rede e quiser limitar o tempo que ela leva.
- Abortando uma solicitação de rede: Você pode usar um controlador de aborto para abortar uma solicitação de rede. A API Fetch fornece uma API AbortController que permite cancelar uma solicitação de rede antes que ela seja concluída.
- Usando uma flag: Você pode usar uma flag no seu código para simular o cancelamento. Você pode definir a flag como true para indicar que a operação deve ser cancelada e, em seguida, verificar a flag no seu código de Promise para determinar se deve continuar ou rejeitar a Promise.
Vale ressaltar que nenhuma dessas técnicas realmente cancela uma Promise; elas simplesmente a rejeitam precocemente. Se você precisar de um cancelamento real, pode ser necessário usar uma biblioteca que forneça suporte para cancelamento, como rxjs ou bluebird.
Cancelamento de Promises com Bluebird
Bluebird é uma biblioteca popular de Promises para JavaScript que fornece recursos avançados, incluindo cancelamento de Promises. O cancelamento de Promises é a capacidade de cancelar uma Promise, o que é útil para cancelar operações assíncronas em andamento ou de longa duração.
Com a ajuda do Bluebird, o cancelamento de Promises é alcançado usando o método Promise.cancel(). Este método não faz parte da API padrão de Promises e é específico para o Bluebird.
Para usar o cancelamento de Promises no Bluebird, é necessário criar uma Promise cancelável usando o construtor new Promise() e passando uma função de cancelamento como argumento. A função de cancelamento será chamada quando a Promise for cancelada.
Veja abaixo um rápido exemplo:
// Cancelamento de Promise com Bluebird const promise = new Promise((resolve, reject, onCancel) => { const id = setTimeout(resolve, 1000) onCancel(() => clearTimeout(id)) }) // Utilizando a Promise como de comum promise.then(() => console.log("Dados")) // Caso queria cancelar a Promise em algum momento promise.cancel()
Múltiplas Promises em Paralelo
Com a ajuda de Promises, é mais fácil gerenciar e executar várias operações assíncronas em paralelo e aguardar que todas sejam concluídas antes de continuar.
Para mostrar o que quero dizer...
São criadas quatro Promises usando a sintaxe new Promise(). Cada Promise é resolvida ou rejeitada após um determinado período de tempo usando o método setTimeout(). A função de erro é definida para registrar qualquer erro no console.
const promise1 = new Promise((resolve) => { setTimeout(() => { resolve("Promise 1 resolvida!") }, 2000); }) const promise2 = new Promise((resolve) => { setTimeout(() => { resolve("Promise 2 resolvida!"); }, 3000); }); const promise3 = new Promise((resolve) => { setTimeout(() => { resolve("Promise 3 resolvida!"); }, 1000); }); const promise4 = new Promise((resolve, reject) => { setTimeout(() => { reject(new Error("Promise 4 rejeitada!")); }, 2000); }); const error = (e) => { console.error(e) }
Depois de criar as Promises acima, agora veremos exemplos de uso de diferentes métodos de Promises.
Método Promise.all()
No exemplo a seguir, é utilizado o método Promise.all(), que recebe um array de Promises e aguarda todas as Promises serem cumpridas. Assim que todas as Promises forem resolvidas, o método then() é executado, o qual retorna um array de valores na ordem em que foram passados no array de Promises. Neste caso, as três Promises são resolvidas e seus valores são registrados no console.
Este é um ótimo método para, por exemplo, buscar dados usando duas solicitações diferentes e, em seguida, combiná-los assim que ambas as solicitações forem concluídas.
Promise.all([promise1, promise2, promise3]) .then((values) => { console.log("Promise.all(): ", values) }) .catch(error)
Método Promise.any()
No exemplo a seguir, é utilizado o método Promise.any(), que recebe um array de Promises e aguarda que qualquer uma delas seja cumprida. Assim que a primeira Promise for resolvida, o método then() é executado, o qual retorna o valor resolvido da primeira Promise. Neste caso, a terceira Promise é resolvida antes das outras duas e seu valor é registrado no console.
Promise.any([promise1, promise2, promise3]) .then((values) => { console.log("Promise.any(): ", values) }) .catch(error)
Método Promise.race()
No exemplo a seguir, é utilizado o método Promise.race(), que recebe um array de Promises e aguarda que a primeira Promise seja resolvida ou rejeitada. Assim que a primeira Promise for resolvida ou rejeitada, o método then() é executado, o qual retorna o valor resolvido ou rejeitado da primeira Promise resolvida ou rejeitada. Neste caso, a terceira Promise é resolvida antes das outras duas, então seu valor é registrado no console.
Promise.race([promise1, promise2, promise3]) .then((values) => { console.log("Promise.race(): ", values) }) .catch(error)
Método Promise.allSettled()
No exemplo a seguir, é utilizado o método Promise.allSettled(), que recebe um array de Promises e aguarda que todas as Promises sejam resolvidas ou rejeitadas. Assim que todas as Promises forem resolvidas ou rejeitadas, o método then() é executado, o qual retorna um array de objetos, cada objeto contendo o status e o valor ou motivo de cada Promise. Neste caso, todas as Promises são resolvidas ou rejeitadas, então o status e os valores ou motivos de todas as Promises são registrados no console.
Promise.allSettled([promise1, promise2, promise3, promise4]) .then((values) => { console.log("Promise.allSettled(): ", values); }) .catch(error)
Conclusão
A API Fetch é uma substituição moderna para o antigo objeto XMLHttpRequest, e é baseada em Promises. Quando você faz uma solicitação com a API Fetch, você recebe de volta uma Promise que é resolvida para o objeto de resposta. Isso permite que você use o método then() para lidar com a resposta de uma maneira limpa e legível.
As funções assíncronas são uma adição mais recente ao JavaScript e são construídas em cima das Promises. As funções assíncronas permitem que você escreva código assíncrono que se parece com código síncrono, tornando mais fácil de ler e escrever. As funções assíncronas usam a palavra-chave await para esperar que as Promises sejam resolvidas antes de continuar, tornando possível escrever código assíncrono que se parece com uma sequência de declarações síncronas.
Em ambos os casos, Promises são usadas para lidar com operações assíncronas de uma maneira limpa e legível. Ao usar Promises, você pode evitar o "callback hell" e escrever código assíncrono que é fácil de entender e raciocinar.