Memoization con tiempo de vida

La memoization ya vimos lo que era y como usarla. Ahora vamos a añadir una característica más que puede resultar útil en algunos casos: “tiempo de vida”. Cada respuesta memorizada solo va a ser válida durante un periodo de tiempo. Pasado ese tiempo el resultado deja de ser válido y si se vuelve a llamar

Todo lo que veamos en este texto esta publicado en la librería memo.js

El primer cambio que vamos a realizar sobre lo que vimos en el anterior post de memoization es que tendremos dos variables para almacenar los datos, una con la cache de los parámetros y sus valores y otra con el momento en que se almacenó esa cache.

La forma de funcionar es la misma que la memoization normal solo que cuando se va recuperar un valor de la cache se comprueba cuanto tiempo hace que se almaceno y si ha transcurrido un tiempo mayor que el tiempo de vida fijado se llama a la función y se actualiza la cache con el valor que devuelva.

Veamos algo de código partiendo de la librería de memoization que creamos.

Lo primero es extender la clase original:

class MemoTime extends Memo {

}

Modificamos el constructor para que cree una variable (lifetime) donde guardar cuando fue la última vez que se actualizo esa clave. También se le tiene que pasar al constructor el tiempo de vida (timeOfLife)

 constructor (func, timeOfLife, keyFunc) {
    super(func, keyFunc);
    this.timeOfLife = timeOfLife || 1000;
    this.lifetime = {};        
 }

También hay que modificar la función que añade una clave – valor para que almacene el momento (new Date()) en que se guarda esa clave.

add(key, value){
    this.lifetime[key] = new Date();
    super.add(key, value);
}

Por último la función que verifica si esta en la cache para que responda ‘false’ en el caso de que este pero haya caducado:

 isInCache(key){
   if(key in this.lifetime) {
      if((new Date() - this.lifetime[key]) < this.timeOfLife){ //¿ha caducado?
         return true;
      }
   } else {
      return false;
   }
}

El resultado final es el siguiente:

class MemoTime extends Memo {
    constructor (func, timeOfLife, keyFunc) {
        super(func, keyFunc);
        this.timeOfLife = timeOfLife || 1000;
        this.lifetime = {};        
    }

    isInCache(key){
        if(key in this.lifetime) {
            if((new Date() - this.lifetime[key]) < this.timeOfLife){
                return true;
            }
        } else {
            return false;
        }
    }

    add(key, value){
        this.lifetime[key] = new Date();
        super.add(key, value);
    }
}

Puede parecer contradictorio tener un mecanismo para almacenar los resultados de una función para no tener que volver a llamarla y que caduquen. Esto es útil cuando el resultado de la función cambia con el tiempo o queremos evitar que a una función se le llame muchas veces seguidas.

Por ejemplo si tenemos un servidor que nos devuelve un dato y no queremos saturarlo de peticiones podemos limitar el número de las mismas usando está técnica. Así cada vez que se llame a la función se tendrá una respuesta pero como mucho se le llamara una vez por cada periodo indicado en el tiempo de vida.

Por último vamos a ver un ejemplo de la clase que hemos creado:

function sum(a,b){  
    console.log("calculating "+a+" + "+b);  
    return a+b;  
}  

let memoTime = new MemoTime(sum, 2000);

console.log(memoTime.call(4,5)); //call sum

console.log(memoTime.call(4,5)); //no call sum
setTimeout(memoTime.call(4,5),1000); //no call sum
setTimeout(memoTime.call(4,5),3000); //call sum