import { mensagemDeErro } from './biblioteca.js'; /** * Parte 6 — Tempo limite, cancelamento e repetição. * * npx tsx src/07-tempo-limite-repeticao.ts */ import { consultarCatalogoExterno, CatalogoIndisponivelError, cronometro, esperar, reiniciarCatalogo, } from './biblioteca.js'; // --------------------------------------------------------------------------- // 1. Tempo limite com Promise.race — desiste de esperar, mas não cancela. // --------------------------------------------------------------------------- class TempoEsgotadoError extends Error { constructor(ms: number) { super(`tempo limite de ${ms} ms esgotado`); this.name = 'TempoEsgotadoError'; } } async function comTempoLimite(tarefa: Promise, ms: number): Promise { let timer: ReturnType | undefined; const estouro = new Promise((_, reject) => { timer = setTimeout(() => reject(new TempoEsgotadoError(ms)), ms); }); try { return await Promise.race([tarefa, estouro]); } finally { clearTimeout(timer); // libera o timer se a tarefa terminar primeiro } } // --------------------------------------------------------------------------- // 2. Cancelamento de verdade com AbortSignal — a tarefa é avisada e para. // É o mesmo sinal que `fetch` aceita, e que o cliente web do Módulo 3 usa. // --------------------------------------------------------------------------- function tarefaLonga(ms: number, signal: AbortSignal): Promise { return new Promise((resolve, reject) => { signal.throwIfAborted(); const cancelar = () => { clearTimeout(id); reject(signal.reason); // o motivo pode ser qualquer valor }; const id = setTimeout(() => { signal.removeEventListener('abort', cancelar); resolve(`terminei em ${ms} ms`); }, ms); signal.addEventListener('abort', cancelar, { once: true }); }); } // --------------------------------------------------------------------------- // 3. Repetição com recuo — para falha transitória, não para falha de domínio. // --------------------------------------------------------------------------- async function comRepeticao( tarefa: () => Promise, tentativas: number, esperaInicial: number, ): Promise { if (!Number.isInteger(tentativas) || tentativas < 1) { throw new RangeError('tentativas deve ser um inteiro positivo'); } if (!Number.isFinite(esperaInicial) || esperaInicial < 0) { throw new RangeError('esperaInicial deve ser finita e não negativa'); } let ultimoErro: unknown; for (let tentativa = 1; tentativa <= tentativas; tentativa += 1) { try { return await tarefa(); } catch (erro) { if (!(erro instanceof CatalogoIndisponivelError)) throw erro; ultimoErro = erro; if (tentativa === tentativas) break; const espera = esperaInicial * 2 ** (tentativa - 1); // 100, 200, 400... console.log(` repetindo em ${espera} ms (tentativa ${tentativa} falhou)`); await esperar(espera); } } throw new Error(`falhou após ${tentativas} tentativas`, { cause: ultimoErro }); } async function principal(): Promise { const marcar = cronometro(); // Tempo limite estourado: a tarefa de 400 ms não cabe em 150 ms. try { await comTempoLimite(esperar(400).then(() => 'pronto'), 150); } catch (erro) { marcar(`race : ${mensagemDeErro(erro)}`); } // Cancelamento: a mesma tarefa, agora avisada de que ninguém mais espera. try { await tarefaLonga(400, AbortSignal.timeout(150)); } catch (erro) { marcar(`AbortSignal: ${(erro instanceof Error ? erro.name : 'erro desconhecido')} — o timer foi cancelado`); } // Repetição: o catálogo externo falha nas duas primeiras chamadas. reiniciarCatalogo(); const capa = await comRepeticao(() => consultarCatalogoExterno('9788501069665'), 4, 100); marcar(`repetição : ${capa.capa}`); } void principal(); /** * A diferença entre os dois primeiros blocos é o ponto da seção. * * `Promise.race` resolve o problema de **quem espera**: sua função para de * esperar e segue a vida. A tarefa original continua correndo até o fim, * consumindo conexão e memória, e o resultado dela é descartado em silêncio. * * `AbortSignal` resolve o problema de **quem trabalha**: a tarefa recebe o * aviso, libera o que estava segurando e rejeita. É o que `fetch` faz quando * recebe `{ signal }`, e é a forma correta quando o trabalho custa caro. * * Sobre repetir: só faz sentido para falha transitória — rede, indisponi- * bilidade momentânea, tempo esgotado. Repetir um 404 quatro vezes é gastar * quatro vezes mais tempo para receber a mesma resposta correta. */