Funciones puras y efectos secundarios en JavaScript | Nicolás Garzón
Una función pura devuelve el mismo resultado para las mismas entradas y no produce efectos secundarios observables fuera de ella.
JavaScript
Copiar function add ( first, second ) {
return first + second;
} Una función pura debe cumplir ambas:
El resultado depende únicamente de sus argumentos y datos locales constantes.
La ejecución no cambia el estado observable del programa o del entorno.
JavaScript
Copiar function calculateSubtotal ( price, quantity ) {
return price * quantity;
} Esta función no es pura porque lee una variable externa mutable.
JavaScript
Copiar let taxRate = 0.19 ;
function calculateTax ( subtotal ) {
return subtotal * taxRate;
} La misma entrada puede producir resultados diferentes si cambia taxRate.
Una versión más explícita:
JavaScript
Copiar function calculateTax ( subtotal, taxRate ) {
return subtotal * taxRate;
} Ahora todas sus dependencias forman parte de la entrada.
JavaScript
Copiar function createId ( ) {
return Math. random ( ) ;
} JavaScript
Copiar function getCurrentTimestamp ( ) {
return Date. now ( ) ;
} Las mismas llamadas pueden producir valores diferentes.
Una función puede recibir esas dependencias:
JavaScript
Copiar function createRecord ( data, generateId, getTimestamp ) {
return {
id : generateId ( ) ,
createdAt : getTimestamp ( ) ,
data,
} ;
} createRecord todavía ejecuta funciones potencialmente impuras, pero sus dependencias quedan explícitas y pueden sustituirse durante una prueba.
Un efecto secundario es una interacción observable que no se limita al valor retornado.
Modificar una variable externa.
Mutar un objeto recibido.
Escribir en una base de datos.
Realizar una solicitud de red.
Modificar el DOM.
Escribir en almacenamiento.
Registrar en consola.
Leer la hora actual.
Generar aleatoriedad.
Lanzar algunos errores según el contrato y contexto.
JavaScript
Copiar function saveUser ( user ) {
database. users. push ( user) ;
} La operación modifica estado externo.
JavaScript
Copiar function applyDiscount ( product ) {
product. price *= 0.9 ;
return product;
} La función modifica el objeto recibido. Otro código que comparte la referencia también observa el cambio.
Una versión sin esa mutación:
JavaScript
Copiar function applyDiscount ( product ) {
return {
... product,
price : product. price * 0.9 ,
} ;
} La copia con spread es superficial; las estructuras anidadas siguen necesitando cuidado.
Una función puede utilizar mutación interna y seguir siendo pura si el estado se crea dentro, no escapa antes de terminar y el resultado solo depende de las entradas.
JavaScript
Copiar function createIndex ( products ) {
const index = { } ;
for ( const product of products) {
index[ product. id] = product;
}
return index;
} index se construye localmente. Ningún código exterior podía observarlo durante el proceso.
Pureza no significa prohibir toda asignación local.
JavaScript
Copiar function getCurrentUserName ( ) {
return session. currentUser. name;
} La función no modifica session, pero el resultado depende de estado externo mutable. Por eso no es pura.
JavaScript
Copiar function getUserName ( user ) {
return user. name;
} La dependencia ahora es explícita.
Una expresión es referencialmente transparente cuando puede reemplazarse por su resultado sin cambiar el comportamiento.
JavaScript
Copiar add ( 2 , 3 ) Puede reemplazarse por 5.
JavaScript
Copiar Date. now ( ) No puede sustituirse permanentemente por un valor sin cambiar lo que hace el programa.
Esta propiedad facilita razonar, probar y combinar funciones.
Las aplicaciones necesitan efectos. El objetivo no es eliminarlos, sino mantenerlos visibles y separados de los cálculos.
JavaScript
Copiar function createOrderPayload ( cart, user ) {
return {
userId : user. id,
items : cart. items. map ( ( { productId, quantity } ) => ( {
productId,
quantity,
} ) ) ,
total : calculateTotal ( cart. items) ,
} ;
}
async function submitOrder ( cart, user ) {
const payload = createOrderPayload ( cart, user) ;
return orderApi. create ( payload) ;
}
createOrderPayload transforma datos.
submitOrder realiza el efecto de red.
La lógica de negocio puede probarse sin hacer solicitudes.
Una estrategia común consiste en:
Recibir datos desde el exterior.
Validar y transformar mediante funciones puras.
Ejecutar efectos en una capa visible.
Convertir la respuesta nuevamente en datos.
JavaScript
Copiar async function completeCheckout ( input ) {
const validation = validateCheckout ( input) ;
if ( ! validation. ok) {
return validation;
}
const payload = createCheckoutPayload ( input) ;
const response = await paymentApi. charge ( payload) ;
return mapPaymentResponse ( response) ;
} Una operación idempotente puede repetirse sin cambiar el resultado final más allá de la primera aplicación.
JavaScript
Copiar function setUserAsActive ( user ) {
return {
... user,
isActive : true ,
} ;
} Aplicarla varias veces produce el mismo estado final.
No toda función pura es idempotente:
JavaScript
Copiar function increment ( number ) {
return number + 1 ;
} increment(increment(1)) no equivale a increment(1).
Pureza e idempotencia son conceptos diferentes.
Una función que lanza un error de forma determinista para la misma entrada sigue siendo predecible, aunque algunas definiciones formales tratan el lanzamiento como un efecto.
JavaScript
Copiar function divide ( first, second ) {
if ( second === 0 ) {
throw new RangeError ( "Cannot divide by zero" ) ;
}
return first / second;
} En código práctico, lo importante es que el contrato sea explícito: devolver un resultado, retornar una estructura de error o lanzar una excepción.
Una función pura puede memoizarse con mayor seguridad porque el resultado depende de sus entradas.
JavaScript
Copiar function memoize ( operation ) {
const cache = new Map ( ) ;
return function memoized ( value ) {
if ( cache. has ( value) ) {
return cache. get ( value) ;
}
const result = operation ( value) ;
cache. set ( value, result) ;
return result;
} ;
} La caché interna es un efecto dentro del wrapper, pero si no es observable y preserva resultados, la función memoizada puede comportarse externamente como la original.
Una función pura suele necesitar únicamente entradas y expectativas.
JavaScript
Copiar expect ( calculateSubtotal ( 25 , 4 ) ) . toBe ( 100 ) ; Una función con efectos puede requerir:
Sustituir dependencias.
Preparar estado.
Simular red o almacenamiento.
Limpiar después de la prueba.
Verificar interacciones además del retorno.
Separar cálculo y efecto reduce esa complejidad.
Una aplicación que nunca produce efectos no podría guardar datos, mostrar interfaces ni comunicarse con servicios.
Estas funciones necesariamente interactúan con el exterior:
JavaScript
Copiar renderDashboard ( ) ;
saveOrder ( ) ;
sendEmail ( ) ;
trackEvent ( ) ; El objetivo es que los efectos sean:
Intencionales.
Localizables.
Nombrados con claridad.
Ejecutados en capas adecuadas.
Fáciles de sustituir durante pruebas.
JavaScript
Copiar function calculateNextStock ( currentStock, movement ) {
const direction = movement. type === "entry" ? 1 : - 1 ;
const nextStock = currentStock + direction * movement. quantity;
if ( nextStock < 0 ) {
return {
ok : false ,
reason : "insufficient-stock" ,
} ;
}
return {
ok : true ,
stock : nextStock,
} ;
}
async function applyStockMovement ( product, movement ) {
const result = calculateNextStock ( product. stock, movement) ;
if ( ! result. ok) {
return result;
}
await inventoryRepository. updateStock ( product. id, result. stock) ;
return result;
} El cálculo puede probarse sin base de datos. La segunda función hace visible el efecto.
Definir función pura únicamente como “función que devuelve algo”.
Creer que no puede usar variables locales mutables.
Considerar pura una función que lee estado externo mutable.
Mutar argumentos sin hacerlo evidente.
Tratar Date.now o Math.random como entradas estables.
Intentar eliminar todos los efectos de una aplicación.
Mezclar transformación, red, almacenamiento y UI en una sola función.
Confundir pureza con idempotencia.
Suponer que una copia superficial vuelve inmutable toda una estructura.
Una función pura depende de entradas explícitas y no produce efectos observables.
Leer estado externo mutable también rompe la pureza.
La mutación local puede ser compatible con pureza si no escapa ni es observable.
Las aplicaciones necesitan efectos; deben mantenerse visibles y aislados.
Un núcleo puro facilita pruebas y razonamiento.
Pureza e idempotencia no significan lo mismo.
La inmutabilidad reduce cambios compartidos inesperados.
Separar cálculo y efecto mejora el diseño sin convertirlo en dogma.
¿Cuáles funciones son puras?
JavaScript
Copiar let taxRate = 0.19 ;
function first ( subtotal ) {
return subtotal * taxRate;
}
function second ( subtotal, taxRate ) {
return subtotal * taxRate;
}
function third ( user ) {
console. log ( user. name) ;
return user. name;
} Respuesta second es pura respecto a esos argumentos. first depende de estado externo mutable y third produce el efecto de escribir en consola.
Programación funcional reúne estas ideas como un estilo de diseño basado en transformación de datos, composición, inmutabilidad y efectos controlados.