Error típica de setTimeout en TypeScript: window vs Node.js

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

«El setTimeout incorrecto»: cómo TypeScript vuelve locos a los desarrolladores

\n

¿Alguna vez has escrito un temporizador simple en TypeScript y el editor de código de repente empezó a subrayar todo en rojo? Escena familiar: importas setTimeout, escribes const timer = setTimeout(...), y TypeScript muestra un error o sugiere un tipo confuso. ¿Cuál es el problema? Resulta que setTimeout tiene dos versiones: una para el navegador (window) y otra para el servidor (Node.js). Y TypeScript, siendo perfeccionista, nos obliga a elegir.

\n

Este tema es uno de los más populares en Stack Overflow. Miles de desarrolladores tropiezan con el mismo error. Pero no solo copiaremos la respuesta. Entenderemos la raíz del problema, comprenderemos cómo funciona la tipificación y aprenderemos a escribir código que no se rompa ni en el navegador ni en Node.js.

\n\n

¿Por qué dos versiones de setTimeout?

\n

Históricamente, JavaScript vive en dos mundos: el del cliente (navegador) y el del servidor (Node.js). En el navegador, setTimeout es un método del objeto global window. En Node.js, es una función del módulo global timers. Hacen lo mismo, pero sus firmas de tipo son diferentes.

\n

Aquí están las diferencias clave:

\n
    \n
  • Valor de retorno: en el navegador, window.setTimeout devuelve un number (identificador del temporizador). En Node.js, devuelve un objeto NodeJS.Timeout.
  • \n
  • Contexto: en el navegador, this dentro del callback es window (en modo no estricto). En Node.js, es el objeto del temporizador.
  • \n
  • Tipificación: TypeScript proporciona diferentes declaraciones para diferentes entornos. Si escribes código universal (aplicación isomórfica), surge un conflicto.
  • \n
\n\n

Cómo se ve el problema en el código

\n

Imagina que estás desarrollando una biblioteca que debe funcionar tanto en el cliente como en el servidor. Escribes 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        // ¿Qué tipo tiene timer? ¿number o NodeJS.Timeout?\n    });\n}
\n

TypeScript verá que setTimeout puede ser de dos tipos y mostrará un error: "Type 'Timeout' is not assignable to type 'number'." O al revés, dependiendo de la configuración de tsconfig.json y los tipos incluidos.

\n

Para que sea más claro, así es como se ven los tipos si los observamos a través de ReturnType:

\n
// En el navegador (lib.dom.d.ts)\ntype BrowserTimer = ReturnType<typeof window.setTimeout>; // number\n\n// En Node.js (@types/node)\ntype NodeTimer = ReturnType<typeof setTimeout>; // NodeJS.Timeout
\n\n

Soluciones correctas para diferentes escenarios

\n

Veamos cuatro enfoques. Elige el que se adapte a tu proyecto.

\n\n

1. Si escribes solo para el navegador

\n

El caso más simple. Simplemente especifica el tipo number y usa window.setTimeout explícitamente:

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

¿Por qué funciona? En el entorno del navegador, window.setTimeout siempre devuelve un número. TypeScript lo sabe si tienes incluida lib: ["dom"].

\n\n

2. Si escribes solo para Node.js

\n

Aquí el tipo es NodeJS.Timeout. Úsalo directamente:

\n
// node-timer.ts\nimport { setTimeout } from 'timers'; // importación explícita para claridad\n\nconst timer: NodeJS.Timeout = setTimeout(() => {\n    console.log('El temporizador se activó');\n}, 2000);\n\nclearTimeout(timer); // clearTimeout acepta NodeJS.Timeout
\n

Importante: En Node.js, clearTimeout también acepta No

Blogs

Book Recommendations