Javascript/Nodejs setInterval with scheduled start, simple cron style

Question:

For JavaScript and NodeJS, there are n libraries that are robust Cron-style, such as node-cron . However, they are too complex for simple situations, are heavy to download in the browser or require an additional dependency on NodeJS, which makes them unfeasible in simpler cases.

I want to create a function that can:

  • Accept the second, minute, and hour that the routine is ready to provide new data.
  • Check what time it is now on the client, and schedule the start of setInterval for the first opportunity the server has new data.
  • Set the setInterval interval to exactly the period between server updates.
  • Run in NodeJS environment and modern browsers and in IE8. If you don't know how to test in NodeJS, I'll test it for you .
  • There must be no additional dependencies. No jQuery or NodeJS package.
  • The code must accept an interval parameter of type retry in x seconds , and pass a callback to the executed function so that if it returns exactly false , it will retry this one until it returns true or arrives at the next default execution time. It considers that the server can fail and always return an error, but the client must avoid overlapping additional attempts!

Example of real use

The code below is responsible for synchronizing a database table with the browser or NodeJS task executed

/**
 * Sincroniza o cliente com dados do banco de dados. Caso os dados sejam
 * diferentes dos que o cliente já possuia antes, passa ao callback true
 * do contrário, se percebeu que o servidor ainda não atualizou os dados
 * retorna false
 *
 * @param   {Function} [cb]  Callback. Opcional
 */
function sincronizar(cb) {
  var conseguiuSincronizar = false;
  // Executa uma rotina de sincronização de dados
  cb && cb(conseguiuSincronizar);
}

However, the database is only updated once every 15 minutes, that is, at 0 , 15 , 30 , 45 minutes of an hour.

As saving to database can be time consuming, this routine would have to run every 15min and a few seconds of delay, for example, every 15min5s .

The problem with using setInterval is that putting it to refresh every 15min runs the risk that when the browser or the NodeJS task is started, there is a delay between the time the client could get new information and the time it is available . Setting setInterval to a period less than 15min would cause data loss.

bootstrap

Below is a bootstrap of how the simplest example could be done.

function cron (cb, s, m, h) {
    var start = 0, interval = 0;

    /* Logica para calculo de start e interval aqui */ 

    setTimeout(function () {
        setInterval(cb, interval);
    }, start);
}

Answer:

function cron(callback, startTime, interval, threshold) {
    function callbackWithTimeout() {
        var timeout = interval === undefined ? null : setTimeout(callbackWithTimeout, interval);
        callback(timeout);
    }
    if (startTime === undefined) {
        // Corre em intervalos a partir do próximo ciclo de eventos
        return setTimeout(callbackWithTimeout, 0);
    }
    // Limitar startTime a hora de um dia
    startTime %= 86400000;
    var currentTime = new Date() % 86400000;
    if (interval === undefined) {
        // Corre uma vez
        // Se startTime é no passado, corre no próximo ciclo de eventos
        // Senão, espera o tempo suficiente
        return setTimeout(callbackWithTimeout, Math.max(0, startTime - currentTime));
    }
    else {
        var firstInterval = (startTime - currentTime) % interval;
        if (firstInterval < 0) firstInterval += interval;
        // Se falta mais do que threshold para a próxima hora,
        // corre no próximo ciclo de eventos, agenda para a próxima hora
        // e depois continua em intervalos
        if (threshold === undefined || firstInterval > threshold) {
            return setTimeout(function () {
                var timeout = setTimeout(callbackWithTimeout, firstInterval);
                callback(timeout);
            }, 0);
        }
        // Senão, começa apenas na próxima hora e continua em intervalos
        return setTimeout(callbackWithTimeout, firstInterval);
    }
}

Use:

// Começar às 00:05:30 em intervalos de 00:15:00,
// mas não correr já se só faltar 00:00:30
// 
// Portanto, nas seguintes horas:
// 00:05:30 00:20:30 00:35:30 00:50:30
// 01:05:30 01:20:30 01:35:30 01:50:30
// 02:05:30 02:20:30 02:35:30 02:50:30
// 03:05:30 03:20:30 03:35:30 03:50:30
// ...
// 23:05:30 23:20:30 23:35:30 23:50:30
// 
// Se a hora actual é 12:00:00, começa já e depois às 12:05:30
// Se a hora actual é 12:05:00, começa só às 12:05:30
cron(function (timeout) { /* ... */ },
     (( 0*60 +  5)*60 + 30)*1000,
     (( 0*60 + 15)*60 +  0)*1000,
     (( 0*60 +  0)*60 + 30)*1000);

// Uma vez apenas às 12:05:30
cron(function (timeout) { /* ... */ },
     ((12*60 +  5)*60 +  0)*1000);

// Repetidamente em intervalos de 00:15:00
cron(function (timeout) { /* ... */ },
     undefined,
     (( 0*60 + 15)*60 +  0)*1000);

The function returns the value of setTimeout so that it can cancel before starting, and the callback receives the value of the new setTimeout when there is repetition, so that it can cancel halfway through. For example, to run 4 times:

var count = 0;
cron(function (timeout) {
         count++;
         if (count == 4) clearTimeout(timeout);
     },
     (( 0*60 +  5)*60 + 30)*1000,
     (( 0*60 + 15)*60 +  0)*1000,
     (( 0*60 +  0)*60 + 30)*1000);
Scroll to Top