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 está dentro de otra función.

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';

const buscarNodo = (nodo) => {
    const data = {};    //estado reusable

    return async (valor) => {   //closure asincrono
        if (valor) {

            const res = data[valor];
            if (res)    
                return res;  //si existe en el estado local se retorna

            else {
                const snap = await firebase
                .database()
                .ref()
                .child(nodo)
                .child(valor)
                .once('value');

                if (snap.exists()) {
                    const result = snap.val();
                    result.key   = valor;
                    data[valor]  = result;  //se argega al estado local

                    return result;  
                }
            }
        }
        
        return false;   //si no existe valor de busqueda 
        //o si no hay resultados en el estado local ni en la api
    }
}

export default buscarNodo;

¿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 😊