Async/Await e reject: por que isso é um problema para iniciantes?
\nVocê já escreveu uma função async, esperou uma resposta do servidor e depois pegou um UnhandledPromiseRejection? Familiar? Este é um dos problemas mais comuns enfrentados por desenvolvedores que estão migrando para async/await no TypeScript.
No Stack Overflow, a pergunta "How to reject in async/await syntax?" acumulou milhares de visualizações. E não é à toa. À primeira vista, parece que basta retornar Promise.reject() — e pronto. Mas em projetos reais é mais complexo: tipos, cadeias de chamadas, perda de contexto de erros.
Neste guia, vamos analisar como rejeitar promessas corretamente em funções async do TypeScript, aprender a diferenciar throw de return Promise.reject() e ver exemplos práticos de projetos reais.
Fundamentos: como funciona o reject em async/await
\nAntes de mergulhar nos detalhes, vamos relembrar o básico. Qualquer função async no TypeScript retorna uma Promise. Se dentro dela ocorrer um throw ou for retornado Promise.reject(), a promessa entra no estado rejected.
// Exemplo simples de reject em async/await\nasync function fetchUser(id: number): Promise<User> {\n if (id <= 0) {\n throw new Error('ID de usuário inválido');\n }\n // ...lógica para obter o usuário\n}\n\nAqui, throw é automaticamente convertido em reject. Mas e se quisermos retornar uma promessa já rejeitada?
throw vs return Promise.reject()
\nNo TypeScript, ambas as abordagens funcionam, mas há nuances:
\n\n- \n
- throw — é a forma mais idiomática. Ele interrompe a execução da função e passa o controle para o
catchmais próximo. \n - return Promise.reject() — retorna explicitamente uma promessa rejeitada. Útil quando você precisa passar um erro de outra função. \n
// Diferença entre throw e return Promise.reject()\nasync function processOrder(orderId: string): Promise<OrderStatus> {\n // Opção 1: throw\n if (!orderId) {\n throw new Error('ID do pedido é obrigatório');\n }\n \n // Opção 2: return Promise.reject\n if (orderId.startsWith('test_')) {\n return Promise.reject(new Error('Pedidos de teste não são permitidos'));\n }\n \n return { status: 'processing' };\n}\n\nImportante: O TypeScript nem sempre infere corretamente os tipos ao usar return Promise.reject(). Se a função espera Promise<T> e você retorna Promise.reject(), pode ocorrer um erro de tipagem. Nesses casos, é melhor usar throw.
Erros típicos ao rejeitar em funções async
\nMesmo desenvolvedores experientes em TypeScript às vezes cometem erros clássicos. Aqui estão os top 3:
\n\n1. Esquecer de tratar o reject no código chamador
\nasync function main() {\n // Ruim: sem try/catch\n const user = await fetchUser(-1);\n console.log(user); // UnhandledPromiseRejection!\n}\n\n// Bom:\nasync function mainSafe() {\n try {\n const user = await fetchUser(-1);\n console.log(user);\n } catch (error) {\n console.error('Falha ao buscar usuário:', error);\n }\n}\n\n2. Tipagem incorreta de erros
\nNo TypeScript, o bloco catch por padrão captura unknown. Isso é correto, mas às vezes os desenvolvedores tentam acessar error.message sem verificação.
try {\n await riskyOperation();\n} catch (error) {\n // Erro: error é unknown\n console.log(error.message); // ❌ A propriedade 'message' não existe no tipo 'unknown'\n \n // Correto:\n if (error instanceof Error) {\n console.log(error.message);\n } else {\n console.log