Contenido

jsCron, portando Cron a Javascript

5 Ene

+ 31

En un momento de aburrimiento he estado implementado una versión de Cron para Javascript. Para los que no lo sepan, Cron es una utilidad del sistema Unix que permite programar tareas a lo largo del tiempo. Una de las utilidades más usadas para tareas como copias de seguridad, envío de mails, …

Para Javascript he pensado que sería interesante usar la misma estructura cron usa en el fichero crontab, donde se almacena la lista de tareas programadas.

35 17 * * * hola()

Hagamos un pequeño repaso a los parámetros de Cron (por orden):

  1. minuto [0-59]
  2. hora [0-23]
  3. dia del mes [0-31]
  4. mes [0-12]
  5. dia semana [0-7]
  6. ejecutable

Los * indican cualquier, por lo tanto en el ejemplo anterior indicamos que cada día a las 17:35 se ejecutará el script hola().

La hora especificada será la del navegador del usuario

Veamos unos ejemplos más:

* 16 * * * hola()

Cada día desde las 16:00 a las 16:59 se ejecutará hola().

30 6 1 * * showHola()

Ejecutamos showHola() el día 1 de cada mes a las 6:30 de la mañana.

Se trata de un sistema bastante rudimentario, pero funciona y aunque operadores como (/2) no están contemplados, dan mucho juego.

Codigo

var jsCron = {
		items:[],
		interval: null,
		parse: function(strUnix) {
				return strUnix.match(/^(\d+|\*) (\d+|\*) (\d+|\*) (\d+|\*) (\d|\*) +(\w+)/);
		},
		check: function() {
				var hoy = new Date();
				var test = [new Date(), hoy.getMinutes(), hoy.getHours(), hoy.getDate(), hoy.getMonth(), hoy.getDay()];

				for (var i in this.items) {
					var exec = 0;
					var t = this.parse(this.items[i][1]);
					for (var x in t)
				    if (t[x] && (t[x] == test[x] || t[x] == "*"))exec++;
					if (exec == 5 && this.items[i][0] == 0) {
							eval(t[6]).call();
							this.items[i][0] = 1;
					} else if (exec < 5 && this.items[i][0] == 1) {
						this.items[i][0] = 0;
					}
				}
		},
		set: function(strUnix) {
			if (!/^(\d+|\*) (\d+|\*) (\d+|\*) (\d+|\*) (\d|\*) +(\w+)/.test(strUnix)) return new Error("Formato invalido");
			this.items.push([0, strUnix]);
		},
		init: function(seg) {
			var seg = seg || 1000;
			this.interval = setInterval("jsCron.check()", seg);
		}
};
jsCron.init();

Modo de uso

Al igual que de un crontab debemos especificar un listado de tareas a programar, para ello usaremos el método set() e introduciremos la sentencia en el formato explicado anteriormente.

// Función hola();
function hola() {
   alert("Hola");
}

// Tarea programada
jsCron.set("35 17 * * * hola()");

Descargar Fichero JS (jscron)

Actualización

Un comentario de Ajaxian, me ha dado una solución al usar un eval() para ejecutar el código. Así podemos usarlo de una forma más cómoda sin perder la legibilidad de la función.

// Tarea programada
jsCron.set("35 17 * * *", hola())
// de forma inline
jsCron.set("35 17 * * *", function() {
   alert("Hola");
});

wooop, eso si que es hacer algo entrentenido en el tiempo que estas aburrido … lo testearé luego a ver q tal funciona =)

@joaquin núñez: No me acaba de gustar que use setInterval() para comprobar cada X seg si hay acciones pendientes, pero no se otra forma.

jo, como mola, te ha salido redondo

Excelente! esto supera enormemente todos los settimeout setinterval y demás (aunque en el fondo son lo mismo, pero no importa) :)

Muy interesante, nunca sospeche que se pudiera hacer algo parecido siquiera

Según veo (que alguien me corrija si me equivoco), no existe permanencia a la hora de establecer una entrada con JsCron y me explico.

Si yo programo que para dentro de 3 días se ejecute un script de bienvenida a mi web (por ejemplo). Y el usuario cierra el navegador, cuando vuelva un día despues no se mantiene la entrada programada, ni tampoco dentro de tres días. Por lo que se obliga a mantener la ventana abierta (y sin recarga de página).

Tal vez guardando una cookie o algo similar se podría conseguir eso, para que cuando se vuelva a entrar, si han pasado 3 días se ejecute dícha entrada de JsCron.

Saludos

Me parece una manera muy ingeniosa de conseguirlo con muy poco código. Te felicito.

@shakaran: Si que se guarda, no hay posibilidad de indicar dentro de X días, sinó que para un hora y un día concreto estableces una entrada.

Osea, solo puedes indicar que cada X minutos y cada Y horas de cada Z día del mes de cada A mes y el día de la seman B se ejecute. De esa forma es como decirle que el día 1 de enero a las 00:00 ejecutes felicitarusuarios(). Da igual las veces que el usuario entre o salga, la fecha ya está definida. Si el usuario está online en el momento que está predefinido entonces se ejecutará.

No se si me he explicado bien.

Saludos

@aNieto2k: No me acaba de gustar que use setInterval() para comprobar cada X seg si hay acciones pendientes, pero no se otra forma.

En el init puedes llamar una función que compruebe cual es la siguiente tarea que hay que ejecutar, y en función del tiempo que falte montar el setInterval.

Una posiblidad para no usar el setInterval cada equis tiempo es no usar un controlador de tareas, sino una “clase” tarea cuyo “constructor” exija una función a ejecutar y una expresión cron.

De esa manera, cada tarea se controla a si misma, usando el setInterval con el tiempo que queda para la siguiente ejecución (calculado en función de la fecha y hora actuales).

Aunque alabo la solución programable en js, lo normal si estais hospedados en cualquier unix es que tengais vuestro propio cron editable desde una consola (aunque tb es verdad que no todos los hostings dan cuenta de shell).

Ojo! No nos confundamos, cron en el sistema es para unas cosas y en las páginas son para otras.

Hola,

mira llevo un rato probando el sistema pero no me funciona bien y no se si es un tema del Script o mio. La cuestion es que he probado la primera version y se ejecuta una vez, pero nada mas:

jsCron.set(”1 * * * *”, hola());

y luego con la ultima version del codigo, se ejecuta al ppio pero luego no repite la opracion.

Los ejemplos estan aqui:

http://www.juanantonio.info/research/web/js/examples/jscron1.htm
http://www.juanantonio.info/research/web/js/examples/jscron2.htm
En el segundo ejemplo en FF me salta un error de Regular Expressions:

if (!/^(\d+|\*) (\d+|\*) (\d+|\*) (\d+|\*) (\d|\*))/.test(strUnix)) return new Error(”Formato invalido”);

Ya me dices algo.

Muchas gracias y buen trabajo.

Hola Again, mi idea es que cada 15 minutos se ejecutara una funcion y empeza a probar para que se ejecute cada minuto, pero en la primera version del script, tengo:

jsCron.set("* * * * * hola()");

y no se ejecuta cada minuto.

http://www.juanantonio.info/research/web/js/examples/jscron1.htm

no se donde puede estar el fallo.

Muchas gracias

@Juan Antonio Breña Moral: Esto es debido a que una vez ejecutada la funcionalidad añado un flag para que no se vuelva a ejecutar.
this.items[i][0] = 1;

Es que creo que para lo que quieres hacer no necesitas jsCron, podrías usar setInterval() directamente y así te ahorras código.

Hola de nuevo,

lo probare, pero creo que tu clase, esta muy bien, quizas, si hubiera un metodo adicional para repetirse la tarea, tu clase seria aun mejor.

De todas formas, te comente que el codigo publicado no me da error de ejecucion de Regular Expressions, pero en el fichero en FF si da un error.

Mañana te comento.

Buen trabajo

@Juan Antonio Breña Moral: Prueba con el fichero que hay ahora, le he añadido un nuevo parámetro a la función para definir si quieres que se repita o no.

jsCron.set("* * * * *", hola, true);

Dime cosas.

Saludos

Hola,

pues mira ahora ya la libreria funciona, pero no se repite.

http://www.juanantonio.info/research/web/js/examples/jscron2.htm

da un fallo en la siguiente Linea:

eval(t[6]).call();

parece como si estuviera vacia y entonces no puede Evaluar la Expresion regular.

Ya estamos cerquisima. :D

Un saludo

El fallo del eval() es mio, ya está solventado en el fichero jscron.js

Revisando el código, he visto que el problema está en como llamas al set(). Debería ser así:

jsCron.set("* * * * *", hola, true);

Si te fijas, no lleva los paréntesis en hola, ya que simplemente indicamos la función, si le añadimos los paréntesis la ejecuta.

function hola(){
return "UNO";
}
var a = hola;
// --> function() {return "UNO";}

var b = hola();
// b = UNO

Espero que te haya quedado más claro ahora.

Saludos

Hola, si efectivamente ahora se ejecuta, pero si hacemos el ejemplo que veiamos:

jsCron.set("* * * * *", hola, true);

se ejecuta iteramente en un bucle y no cada 1 min. jejeje

probe este ejemplo tb

jsCron.set("1 * * * *", hola, true);

pero supongo que se ejecutara en el minuto 1 de cada hora.

En el ejemplo he puesto directamente el link al Js.

http://www.anieto2k.com/demo/jscron/jscron.js
http://www.juanantonio.info/research/web/js/examples/jscron2.htm

supongo que en el caso de “* * * * *” si el primer valor es * se deberia calcular como repetir cada 60m, digo vaya.

Un saludo.

@Juan Antonio Breña Moral: Es que Cron no funciona para hacer eso. Osea puedes hacer que se ejecute en un tiempo determinado, no puedes definir un cada tiempo (por lo menos jsCron no está pensado para eso).

Si lo que quieres hacer es que se ejecute cada 5 min, lo que tendrás que usar es setInterval() y te ahorrarás comprobaciones, y código inútil que no está pensado para eso.

Espero haberte aclarado algo.

saludos

Buenos días,

Si observas, en el ejemplo 1:
http://www.juanantonio.info/research/web/js/examples/jscron1.htm

ya consigo hacer lo que te comentaba: Ejecutar una tarea cada minuto.

			jsCron.set("1 * * * * hola()");
			jsCron.set("2 * * * * hola()");
			jsCron.set("3 * * * * hola()");
...
...
			jsCron.set("59 * * * * hola()");

Este ejemplo emplea, el codigo original. en cambio con la libreria que tienes colgada, no es posible:

http://www.juanantonio.info/research/web/js/examples/jscron2.htm

aun habiendo hecho los cambios:

			jsCron.set("1 * * * *", hola, true);
			jsCron.set("2 * * * *", hola, true);
			jsCron.set("3 * * * *", hola, true);
...
...
			jsCron.set("59 * * * *", hola, true);

Personalmente a mi me gusta jsCron, ¿Que pasa en la libreria para que no funcione en el ejemplo2?

En un codigo de produccion, quizas cada minuto no seria necesario, pero si por ejemplo cada 15m. o una temporizacion mayor. De todas formas las librerias de Ajax actuales como por ejemplo Prototype o JQuery traen utilidades para actualizar contenidos, pero JsCron esta bien para tareas especificas con independencia de Frameworks de Javascript.

Seria posible revisar la actual libria para que funcionara como en el codigo original?

Muchas gracias por tu trabajo.

@Juan Antonio Breña Moral: prueba con esto:


jsCron.set(”1 * * * *”, hola);
jsCron.set(”2 * * * *”, hola);
jsCron.set(”3 * * * *”, hola);
...

Hola, perdona la demora, pero esta semana fue horrible. Fantastico, funciona de cine! Lo estoy probando ahora mismo y genial.

Que sepas, que me ha dado muchas ideas.

Buen fin de semana.

Alguien sabria como se podria hacer para ejecutar este js cuando x ej se carga un ws por ej? utilizo tomcat6 y el ws/webapp esta realizado en java (spring, struts, hibernate, axis2, etc, etc)

Desde ya muchas gracias!

Comentar

#

Me reservo el derecho de eliminar y/o modificar los comentarios que contengan lenguaje inapropiado, spam u otras conductas no apropiadas en una comunidad civilizada. Si tu comentario no aparece, puede ser que akismet lo haya capturado, cada día lo reviso y lo coloco en su lugar. Siento las molestias.