Erro típico de setTimeout em TypeScript: window vs Node.js

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

"O setTimeout errado": como o TypeScript enlouquece os desenvolvedores

\n

Você 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.

\n

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\n

Por que duas versões de setTimeout?

\n

Historicamente, 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.

\n

Aqui estão as principais diferenças:

\n
    \n
  • Valor de retorno: no navegador, window.setTimeout retorna number (identificador do timer). No Node.js, retorna um objeto NodeJS.Timeout.
  • \n
  • Contexto: no navegador, this dentro 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
\n\n

Como o problema aparece no código

\n

Imagine 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}
\n

O 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.

\n

Para ficar mais claro, veja como os tipos aparecem quando observados através de ReturnType:

\n
// 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\n

Soluções corretas para diferentes cenários

\n

Vamos considerar quatro abordagens. Escolha a que se adequa ao seu projeto.

\n\n

1. Se você escreve apenas para o navegador

\n

O caso mais simples. Basta especificar o tipo number e usar window.setTimeout explicitamente:

\n
// browser-timer.ts\nconst timerId: number = window.setTimeout(() => {\n    console.log('Passou um segundo');\n}, 1000);\n\n// Para limpar\nwindow.clearTimeout(timerId);
\n

Por que isso funciona? No ambiente do navegador, window.setTimeout sempre retorna um número. O TypeScript sabe disso se você tiver lib: ["dom"] configurado.

\n\n

2. Se você escreve apenas para Node.js

\n

Aqui, o tipo é NodeJS.Timeout. Use-o diretamente:

\n
// 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
\n

Importante: No Node.js, clearTimeout também aceita No

Blogs

Book Recommendations