Ejemplo práctico de Closure

Abdiel Martinez |

¿Qué es un closure y cómo puedes ocuparlos en tu código?

Qué es un closure en JS?

Empecemos por definir que es un closure (algunos los llaman como clausura o cerradura yo prefiero llamarle closure 😅). En pocas palabas un closure es:

Un closure es una función que "recuerda" y mantiene acceso a las variables del contexto donde fue creada

Simple y al grano, es una función dentro de otra. Ahora bien, ¿para qué nos puede servir hacer un closure en nuestro código? La principal ventaja es que nos permite tener variables en la función padre que solo serán accesibles desde la función interna (hija), además de poder acceder también a los parametros de la función padre. Esto nos permite tener un estado privado dentro de nuestra función pero más interesante aún que este sea un estado reusable.

Una parte importante de los closures es que deben ser retornados por la función padre hacia el exterior, a esto se le conoce como función de alto nivel o en inglés High Order Functions, te invito investigar más sobre ello.

Regresando a los closures un ejemplo sencillo sería:

const contador = () => {   //funcion padre
    let valor = 0;      //estado privado y reusable

    return ()=>{        //closure o funcion hija
        return valor++;
    }
}

Ahora para utilizar nuestro closure:

const contar = contador();  //guardamos el closure en la constante contar

console.log(contar())   //0
console.log(contar())   //1
console.log(contar())   //2

Cada vez que llamamos contar() accedemos al closure, se retorna su valor actual y luego suma 1 a la variable valor.

Pruebalo

See the Pen Closure by Abdiel Martinez (@anb98) on CodePen.

A la práctica

Aunque desde que aprendí este concepto me pareció muy interesante, aún no había conseguido encontrar un caso práctico, finalmente encontré uno que podría serle util a más de alguno.

El problema consiste en realizar consultas a una api, en mi caso realtime de firebase, donde cada resultado quede disponible para que en la siguiente vez que se solicite el mismo dato en lugar de volver a llamar a la api, el resultado se devuelva desde el estado local, reduciendo así el tiempo de respuesta.

Mi solución:

import firebase from 'firebase';

import { getDatabase, ref, child, get } from 'firebase/database';

export const searchNode = (node) => {
    const data = {};    // estado reusable y privado
    return async (value) => {   // closure asíncrono

        if (!value) // si no se pasa un valor, no hay nada que buscar
            return false;

        const memoizedValue = data[value];
        if (memoizedValue)    
            return memoizedValue;  // si ya existe en el estado local, retornamos el resultado

        const db = getDatabase();
        const dbRef = ref(db);
        
        try {
            const snapshot = await get(child(dbRef, `${node}/${value}`));
            
            if (snapshot.exists()) {
                const result = snapshot.val();
                result.key = value;
                data[value] = result;  // guardamos el resultado en el estado local
                return result;  
            }
        } catch (error) {
            console.error('Error fetching data:', error);
            return false;
        }
        
    }
}

¿Qué le mejorarías?🤔

Para utilizarlo podría ser:

const buscarAlumno   = buscarNodo('alumnos');
const buscarMateria  = buscarNodo('materias');
const buscarProfesor = buscarNodo('profesores');

(async ()=>{  
    // esto es conocido como funcion invocada 
    // inmediatamente, en ingles: 
    // Immediately invoked function expression (IIFE)


    const alumnoEncontrado   = await buscarAlumno( 'idAlumno');
    const materiaEncontrada  = await buscarMateria( 'idMateria' );
    const profesorEncontrado = await buscarProfesor( 'idProfesor' );

})();

Este es mi caso particular, la llamada a firebase podría ser sustituida por cualquier otra api.

¿Qué otra idea se te ocurre para utilizar closures? Compártelo en los comentarios 😊