"O setTimeout errado": como o TypeScript enlouquece os desenvolvedores
\nVocê já escreveu um timer simples em TypeScript e o editor de código de repente começou a sublinhar tudo em vermelho? Cena familiar: você importa setTimeout, escreve const timer = setTimeout(...), e o TypeScript mostra um erro ou sugere um tipo estranho. Qual é o problema? Acontece que setTimeout tem duas versões — uma para o navegador (window), outra para o servidor (Node.js). E o TypeScript, sendo perfeccionista, nos obriga a escolher.
Este tópico é um dos mais populares no Stack Overflow. Milhares de desenvolvedores tropeçam no mesmo erro. Mas não vamos apenas copiar a resposta. Vamos entender a raiz do problema, compreender como a tipagem funciona e aprender a escrever código que não quebre nem no navegador nem no Node.js.
\n\nPor que duas versões de setTimeout?
\nHistoricamente, o JavaScript vive em dois mundos: cliente (navegador) e servidor (Node.js). No navegador, setTimeout é um método do objeto global window. No Node.js, é uma função do módulo global timers. Eles fazem a mesma coisa, mas suas assinaturas de tipo são diferentes.
Aqui estão as principais diferenças:
\n- \n
- Valor de retorno: no navegador,
window.setTimeoutretornanumber(identificador do timer). No Node.js, retorna um objetoNodeJS.Timeout. \n - Contexto: no navegador,
thisdentro do callback éwindow(no modo não estrito). No Node.js, é o objeto do timer. \n - Tipagem: o TypeScript fornece declarações diferentes para ambientes diferentes. Se você escreve código universal (aplicação isomórfica), ocorre um conflito. \n
Como o problema aparece no código
\nImagine que você está desenvolvendo uma biblioteca que deve funcionar tanto no cliente quanto no servidor. Você escreve este código:
\n// utils.ts\nexport function delay(ms: number): Promise<void> {\n return new Promise(resolve => {\n const timer = setTimeout(resolve, ms);\n // Qual é o tipo de timer? number ou NodeJS.Timeout?\n });\n}\nO TypeScript verá que setTimeout pode ser de dois tipos e exibirá um erro: "Type 'Timeout' is not assignable to type 'number'." Ou o contrário, dependendo das configurações do tsconfig.json e dos tipos incluídos.
Para ficar mais claro, veja como os tipos aparecem quando observados através de ReturnType:
// No navegador (lib.dom.d.ts)\ntype BrowserTimer = ReturnType<typeof window.setTimeout>; // number\n\n// No Node.js (@types/node)\ntype NodeTimer = ReturnType<typeof setTimeout>; // NodeJS.Timeout\n\nSoluções corretas para diferentes cenários
\nVamos considerar quatro abordagens. Escolha a que se adequa ao seu projeto.
\n\n1. Se você escreve apenas para o navegador
\nO caso mais simples. Basta especificar o tipo number e usar window.setTimeout explicitamente:
// browser-timer.ts\nconst timerId: number = window.setTimeout(() => {\n console.log('Passou um segundo');\n}, 1000);\n\n// Para limpar\nwindow.clearTimeout(timerId);\nPor que isso funciona? No ambiente do navegador, window.setTimeout sempre retorna um número. O TypeScript sabe disso se você tiver lib: ["dom"] configurado.
2. Se você escreve apenas para Node.js
\nAqui, o tipo é NodeJS.Timeout. Use-o diretamente:
// node-timer.ts\nimport { setTimeout } from 'timers'; // import explícito para clareza\n\nconst timer: NodeJS.Timeout = setTimeout(() => {\n console.log('Timer disparou');\n}, 2000);\n\nclearTimeout(timer); // clearTimeout aceita NodeJS.Timeout\nImportante: No Node.js, clearTimeout também aceita No