Desafio TypeScript
Função genérica com Awaited e tratamento de erros tipados
Enunciado
Implemente uma função genérica async unwrap<T>(value: T | Promise<T>): Promise<Awaited<T>> que retorna o valor resolvido. Além disso, crie uma classe TimeoutError e uma função async withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> que rejeita com TimeoutError se a promise não resolver dentro de ms milissegundos. Teste ambos os casos e trate os erros adequadamente.
Requisitos
unwrapdeve aceitarT | Promise<T>e retornarPromise<Awaited<T>>.withTimeoutdeve lançarTimeoutErrorse o tempo excederms.- O
catchdeve diferenciarTimeoutErrorde outros erros. - O programa deve demonstrar um caso de sucesso e um caso de timeout.
Código inicial
class TimeoutError extends Error {
// complete
}
async function unwrap<T>(value: T | Promise<T>): Promise<Awaited<T>> {
// complete
}
async function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
// complete
}
async function main() {
// testes
}
main();
Saída esperada
Valor resolvido: 42
Timeout: operação excedeu 100ms
Valor resolvido: 10
Ver dica
Para withTimeout, use Promise.race entre a promise original e uma promise que rejeita após ms. Lembre-se de tipar o erro como unknown no catch.
Mostrar solução
class TimeoutError extends Error {
constructor(message: string) {
super(message);
this.name = "TimeoutError";
}
}
async function unwrap<T>(value: T | Promise<T>): Promise<Awaited<T>> {
return await value;
}
async function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
const timeout = new Promise<never>((_, reject) =>
setTimeout(() => reject(new TimeoutError(`operação excedeu ${ms}ms`)), ms)
);
return Promise.race([promise, timeout]);
}
async function main(): Promise<void> {
try {
const resolved = await unwrap(Promise.resolve(42));
console.log(`Valor resolvido: ${resolved}`);
// Simula uma promise que demora 200ms
const slow = new Promise<number>((resolve) => setTimeout(() => resolve(99), 200));
await withTimeout(slow, 100); // deve lançar TimeoutError
} catch (error: unknown) {
if (error instanceof TimeoutError) {
console.error(`Timeout: ${error.message}`);
} else {
console.error("Erro inesperado:", error);
}
}
// Teste de sucesso com withTimeout
try {
const fast = new Promise<number>((resolve) => setTimeout(() => resolve(10), 50));
const result = await withTimeout(fast, 100);
console.log(`Valor resolvido: ${result}`);
} catch (error: unknown) {
if (error instanceof TimeoutError) {
console.error(`Timeout: ${error.message}`);
} else {
console.error("Erro inesperado:", error);
}
}
}
main();
Passo a passo
- Definimos
TimeoutErrorestendendoErrorcomnamepersonalizado. unwrapusaawaitpara resolver o valor, seja ele uma promise ou um valor direto; o tipo de retorno éPromise<Awaited<T>>.withTimeoutcria uma promise que rejeita apósmscomTimeoutError.- Usamos
Promise.racepara correr a promise original e o timeout; a primeira que resolver ou rejeitar define o resultado. - No
main, testamosunwrapcom uma promise que resolve 42. - Testamos
withTimeoutcom uma promise lenta (200ms) e timeout de 100ms — deve lançarTimeoutError. - No
catch, tratamos especificamenteTimeoutErrore exibimos a mensagem. - Testamos
withTimeoutcom uma promise rápida (50ms) e timeout de 100ms — deve resolver 10.
Por que funciona
Awaited<T> extrai o tipo resolvido de uma promise, permitindo que unwrap seja genérica e segura. Promise.race resolve ou rejeita com o primeiro resultado, então o timeout interrompe a espera. O tratamento de erros com instanceof garante que apenas TimeoutError seja capturado como timeout, mantendo outros erros visíveis.
Erros comuns
- Não usar
Promise.racecorretamente:Promise.race([promise, timeout])sem tipar o timeout comoPromise<never>pode causar erro de tipo. - Esquecer de rejeitar a promise de timeout:
setTimeout(() => resolve(), ms)faria o timeout resolver em vez de rejeitar. - No
catch, assumir queerroréErrore acessar.messagediretamente — compilação falha porqueerroréunknown. - Não limpar o timeout após a promise original resolver: isso pode causar comportamento inesperado em programas longos, mas não afeta o resultado aqui.
Outra forma de resolver
Usar AbortController com fetch ou operações que suportam cancelamento:
const controller = new AbortController();
setTimeout(() => controller.abort(), ms);
Essa abordagem é preferível quando a operação subjacente pode ser cancelada de fato, evitando trabalho desnecessário.
Saída esperada
Valor resolvido: 42
Timeout: operação excedeu 100ms
Valor resolvido: 10