[Medium] 📄 Promise
O que é uma Promise?
Promise é uma nova funcionalidade do ES6, utilizada principalmente para resolver o problema do callback hell e tornar o código mais fácil de ler. Uma Promise representa a conclusão ou falha eventual de uma operação assíncrona, juntamente com seu valor resultante.
Uma Promise tem três estados:
- pending (pendente): Estado inicial
- fulfilled (cumprida): Operação concluída com sucesso
- rejected (rejeitada): Operação falhou
Uso básico
Criar uma Promise
const myPromise = new Promise((resolve, reject) => {
// Operação assíncrona
const success = true;
if (success) {
resolve('Sucesso!'); // Mudar o estado da Promise para fulfilled
} else {
reject('Falha!'); // Mudar o estado da Promise para rejected
}
});
myPromise
.then((result) => {
console.log(result); // 'Sucesso!'
})
.catch((error) => {
console.log(error); // 'Falha!'
});
Aplicação prática: Processar requisições API
// Criar uma função comum para processar requisições API
function fetchData(url) {
return fetch(url)
.then((response) => {
// Verificar se a response está na faixa 200 ~ 299
if (!response.ok) {
throw new Error('Network response was not ok ' + response.statusText);
}
return response.json(); // Converter a response para JSON e retornar
})
.catch((error) => {
// Verificar se a rede está com problemas ou se a requisição foi rejeitada
console.log('There has been a problem with your fetch operation:', error);
throw error; // Lançar o erro
});
}
fetchData('https://jsonplaceholder.typicode.com/users/1')
.then((userData) => {
console.log('User data received:', userData);
})
.catch((error) => {
console.log('Error:', error.message);
});
Métodos da Promise
.then() / .catch() / .finally()
promise
.then((result) => {
// Tratar o caso de sucesso
return result;
})
.catch((error) => {
// Tratar erros
console.error(error);
})
.finally(() => {
// Sempre executado, independentemente de sucesso ou falha
console.log('Promise concluída');
});
Promise.all()
Só é cumprida quando todas as Promise são concluídas. Falha se qualquer uma falhar.
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve) =>
setTimeout(() => resolve('foo'), 100)
);
const promise3 = Promise.resolve(42);
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values); // [3, 'foo', 42]
});
Quando usar: Quando é necessário esperar que múltiplas requisições API sejam concluídas antes de continuar a execução.
Promise.race()
Retorna o resultado da primeira Promise concluída (seja sucesso ou falha).
const promise1 = new Promise((resolve) =>
setTimeout(() => resolve('Número 1'), 500)
);
const promise2 = new Promise((resolve) =>
setTimeout(() => resolve('Número 2'), 100)
);
Promise.race([promise1, promise2]).then((value) => {
console.log(value); // 'Número 2' (porque foi concluída mais rápido)
});
Quando usar: Definir timeout de requisição, obter apenas a resposta mais rápida.
Promise.allSettled()
Espera que todas as Promise sejam concluídas (sucesso ou falha) e retorna todos os resultados.
const promise1 = Promise.resolve(3);
const promise2 = Promise.reject('Erro');
const promise3 = Promise.resolve(42);
Promise.allSettled([promise1, promise2, promise3]).then((results) => {
console.log(results);
// [
// { status: 'fulfilled', value: 3 },
// { status: 'rejected', reason: 'Erro' },
// { status: 'fulfilled', value: 42 }
// ]
});
Quando usar: Quando é necessário conhecer os resultados de todas as Promise, mesmo que algumas falhem.
Promise.any()
Retorna a primeira Promise bem-sucedida. Só falha se todas falharem.
const promise1 = Promise.reject('Erro 1');
const promise2 = new Promise((resolve) =>
setTimeout(() => resolve('Sucesso'), 100)
);
const promise3 = Promise.reject('Erro 2');
Promise.any([promise1, promise2, promise3]).then((value) => {
console.log(value); // 'Sucesso'
});
Quando usar: Múltiplos recursos de backup, basta que um tenha sucesso.
Perguntas de entrevista
Pergunta 1: Encadeamento de Promise e tratamento de erros
Determine a saída do código a seguir:
Promise.resolve(1)
.then((x) => x + 1)
.then(() => {
throw new Error('My Error');
})
.catch((e) => 1)
.then((x) => x + 1)
.then((x) => console.log(x))
.catch((e) => console.log('This will not run'));
Análise
Vamos analisar o processo de execução passo a passo:
Promise.resolve(1) // Valor de retorno: 1
.then((x) => x + 1) // x = 1, retorna 2
.then(() => {
throw new Error('My Error'); // Lança erro, vai para o catch
})
.catch((e) => 1) // Captura o erro, retorna 1 (Importante: aqui retorna um valor normal)
.then((x) => x + 1) // x = 1, retorna 2
.then((x) => console.log(x)) // Exibe 2
.catch((e) => console.log('This will not run')); // Não é executado
Resposta: 2
Conceitos-chave
- catch captura o erro e retorna um valor normal: Quando
catch()retorna um valor normal, a cadeia de Promise continua executando os.then()seguintes - then após catch continua executando: Porque o erro já foi tratado, a cadeia de Promise volta ao estado normal
- O último catch não é executado: Porque nenhum novo erro foi lançado
Se quiser que o erro continue se propagando, é necessário relançá-lo no catch:
Promise.resolve(1)
.then((x) => x + 1)
.then(() => {
throw new Error('My Error');
})
.catch((e) => {
console.log('Erro capturado');
throw e; // Relançar o erro
})
.then((x) => x + 1) // Não é executado
.then((x) => console.log(x)) // Não é executado
.catch((e) => console.log('This will run')); // É executado
Pergunta 2: Event Loop e ordem de execução
Esta pergunta inclui conceitos de Event Loop
Determine a saída do código a seguir:
function a() {
console.log('Warlock');
}
function b() {
console.log('Druid');
Promise.resolve().then(() => {
console.log('Rogue');
});
}
function c() {
console.log('Mage');
}
function d() {
setTimeout(c, 100);
const temp = Promise.resolve().then(a);
console.log('Warrior');
setTimeout(b, 0);
}
d();
Entendendo a ordem de execução
Primeiro vejamos d():
function d() {
setTimeout(c, 100); // 4. Macro task, atraso 100ms, executado por último
const temp = Promise.resolve().then(a); // 2. Micro task, executado após código síncrono
console.log('Warrior'); // 1. Execução síncrona, saída imediata
setTimeout(b, 0); // 3. Macro task, atraso 0ms, mas ainda é macro task
}
Análise da ordem de execução:
- Código síncrono:
console.log('Warrior')→ ExibeWarrior - Micro task:
Promise.resolve().then(a)→ Executaa(), exibeWarlock - Macro task:
setTimeout(b, 0)é executado primeiro (atraso 0ms)- Executa
b(), exibeDruid Promise.resolve().then(...)dentro deb()é um micro task, executado imediatamente, exibeRogue
- Macro task:
setTimeout(c, 100)executado por último (atraso 100ms), exibeMage
Resposta
Warrior
Warlock
Druid
Rogue
Mage
Conceitos-chave
- Código síncrono > Micro task (Promise) > Macro task (setTimeout)
.then()da Promise pertence a micro task, é executado após o macro task atual terminar e antes do próximo macro task começarsetTimeoutmesmo com atraso de 0 ainda é macro task, executado após todos os micro tasks
Pergunta 3: Síncrono e assíncrono no construtor de Promise
Determine a saída do código a seguir:
function printing() {
console.log(1);
setTimeout(function () {
console.log(2);
}, 1000);
setTimeout(function () {
console.log(3);
}, 0);
new Promise((resolve, reject) => {
console.log(4);
resolve(5);
}).then((foo) => {
console.log(6);
});
console.log(7);
}
printing();
// output ?
Atenção ao bloco Promise
O ponto-chave desta questão: O código dentro do construtor de Promise é executado sincronamente, apenas .then() e .catch() são assíncronos.
Análise da ordem de execução:
console.log(1); // 1. Síncrono, exibe 1
setTimeout(() => console.log(2), 1000); // 5. Macro task, atraso 1000ms
setTimeout(() => console.log(3), 0); // 4. Macro task, atraso 0ms
new Promise((resolve, reject) => {
console.log(4); // 2. Síncrono! Dentro do construtor de Promise é síncrono, exibe 4
resolve(5);
}).then((foo) => {
console.log(6); // 3. Micro task, exibe 6
});
console.log(7); // 3. Síncrono, exibe 7
Fluxo de execução:
- Execução síncrona: 1 → 4 → 7
- Micro task: 6
- Macro task (por tempo de atraso): 3 → 2
Resposta
1
4
7
6
3
2
Conceitos-chave
- O código dentro do construtor de Promise é executado sincronamente:
console.log(4)não é assíncrono - Apenas
.then()e.catch()são assíncronos: Pertencem a micro tasks - Ordem de execução: Código síncrono → micro task → macro task
Armadilhas comuns
1. Esquecer return
Esquecer return em uma cadeia de Promise faz com que o .then() seguinte receba undefined:
// ❌ Errado
fetchUser()
.then((user) => {
fetchPosts(user.id); // Esqueceu return
})
.then((posts) => {
console.log(posts); // undefined
});
// ✅ Correto
fetchUser()
.then((user) => {
return fetchPosts(user.id); // Lembrar do return
})
.then((posts) => {
console.log(posts); // Dados corretos
});
2. Esquecer catch para erros
Erros de Promise não capturados causam UnhandledPromiseRejection:
// ❌ Pode causar erros não capturados
fetchData()
.then((data) => {
return processData(data);
})
.then((result) => {
console.log(result);
});
// ✅ Adicionar catch
fetchData()
.then((data) => {
return processData(data);
})
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error('Ocorreu um erro:', error);
});
3. Abuso do construtor de Promise
Não é necessário encapsular em Promise funções que já retornam uma Promise:
// ❌ Encapsulamento desnecessário
function fetchData() {
return new Promise((resolve, reject) => {
fetch(url)
.then((response) => resolve(response))
.catch((error) => reject(error));
});
}
// ✅ Retornar diretamente
function fetchData() {
return fetch(url);
}
4. Encadear múltiplos catch
Cada catch() só captura erros anteriores a ele:
Promise.resolve()
.then(() => {
throw new Error('Error 1');
})
.catch((e) => {
console.log('Capturado:', e.message); // Capturado: Error 1
})
.then(() => {
throw new Error('Error 2');
})
.catch((e) => {
console.log('Capturado:', e.message); // Capturado: Error 2
});
Tópicos relacionados
- async/await - Açúcar sintático mais elegante para Promise
- Event Loop - Compreensão profunda do mecanismo assíncrono do JavaScript