Cuando el navegador no es un juguete, sino un centro de datos
\n\nEstamos acostumbrados a que JavaScript viva en el sandbox del navegador: animaciones, clics, formularios. Pero ¿qué pasa si te digo que con JS puro se puede procesar un archivo CSV de 500 MB, construir un gráfico con 100 000 puntos y no colgar la pestaña? Esto no es ciencia ficción, sino un uso inteligente del procesamiento de grandes datos en JavaScript.
\nEn este artículo te mostraré cómo trabajar con grandes volúmenes de datos directamente en el navegador, sin backend, sin Python, sin copiar gigabytes al servidor. Aprenderás sobre streaming, Web Workers, optimización de memoria y bibliotecas que convierten el navegador en un mini procesador de datos. ¡Allá vamos!
\n\nPor qué el procesamiento "ingenuo" de datos mata el rendimiento
\nImagina: cargas un archivo CSV de 300 MB y escribes:
\nconst data = await response.json(); // ❌ Error fatal\n¿Qué sucede? JS intenta analizar todo el archivo en la memoria RAM. Si el usuario tiene 4 GB de RAM, el navegador se caerá. Incluso en una PC potente, el recolector de basura comenzará a "ralentizar" y la interfaz se congelará por un minuto.
\nRegla n.º 1: no cargues todo de una vez. Los grandes datos en JavaScript requieren procesamiento por flujo (streaming) y división en fragmentos.
\n\nStreaming de datos: leyendo el archivo por partes
\nLa API del navegador FileReader permite leer el archivo en partes. Pero aún más conveniente es ReadableStream. Ejemplo: procesamiento de un archivo JSON grande línea por línea.
Ejemplo: streaming de JSON con NDJSON
\nEl formato NDJSON (cada línea es un objeto JSON independiente) es ideal para grandes datos. Así es como se lee en flujo:
\nasync function* streamJSON(file) {\n const stream = file.stream();\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop(); // resto en el buffer\n for (const line of lines) {\n if (line.trim()) yield JSON.parse(line);\n }\n }\n}\n\n// Uso\nfor await (const obj of streamJSON(selectedFile)) {\n processRow(obj); // procesamos un objeto a la vez\n}\n\nLa memoria apenas crece: mantenemos solo una línea en ella. Este enfoque es la base del procesamiento de grandes datos en JavaScript en proyectos reales.
\n\nWeb Workers: no congelamos la interfaz
\nIncluso el streaming se ejecuta en el hilo principal. Si los cálculos son complejos, la UI se congela. Solución: Web Workers. Es un hilo separado donde se pueden procesar gigabytes sin retrasos.
\n\nCreando un Worker para procesar CSV
\nCódigo del worker (worker.js):
\nself.onmessage = async function(e) {\n const { file, chunkSize } = e.data;\n const reader = new FileReaderSync(); // lectura síncrona — permitida en worker\n let offset = 0;\n const results = [];\n\n while (offset < file.size) {\n const blob = file.slice(offset, offset + chunkSize);\n const text = reader.readAsText(blob);\n const lines = text.split('\\n');\n for (const line of lines) {\n if (line.trim()) results.push(parseCSVLine(line));\n }\n offset += chunkSize;\n // Enviamos resultado intermedio\n self.postMessage({ type: 'chunk', data: results.splice(0, results.length) });\n }\n self.postMessage({ type: 'done' });\n};\n\nfunction parseCSVLine(line) {\n const [name, age, city] = line.split(',');\n return { name, age: parseInt(age), city };\n}\n\nHilo principal:
\nconst worker = new Worker('worker.js');\nworker.postMessage({ file: csvFile, chunkSize: 1024 * 1024 }); // fragmentos de 1 MB\n\nworker.onmessage = (e) => {\n if (e.data.type === 'chunk') {\n updateUI(e.data.data); // actualizamos tabla/gráfico suavemente\n }\n};\n\nAhora la interfaz sigue siendo receptiva, incluso si procesamos 2 GB de datos. Esta es una técnica clave para el b