«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.
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?
\nHistó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.
Aquí están las diferencias clave:
\n- \n
- Valor de retorno: en el navegador,
window.setTimeoutdevuelve unnumber(identificador del temporizador). En Node.js, devuelve un objetoNodeJS.Timeout. \n - Contexto: en el navegador,
thisdentro del callback eswindow(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
Cómo se ve el problema en el código
\nImagina 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}\nTypeScript 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.
Para que sea más claro, así es como se ven los tipos si los observamos a través de ReturnType:
// 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\nSoluciones correctas para diferentes escenarios
\nVeamos cuatro enfoques. Elige el que se adapte a tu proyecto.
\n\n1. Si escribes solo para el navegador
\nEl caso más simple. Simplemente especifica el tipo number y usa window.setTimeout explícitamente:
// 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"].
2. Si escribes solo para Node.js
\nAquí el tipo es NodeJS.Timeout. Úsalo directamente:
// 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\nImportante: En Node.js, clearTimeout también acepta No