cron-master
TypeScript icon, indicating that this package has built-in type declarations

1.0.1 • Public • Published

cron-master

build-status npm version JavaScript Style Guide !Dependency Scan TypeScript

cron-master provides a standardised way to manage your Node.js CronJobs created using the cron module.

Typically in projects we'll see instances of CronJob from the fantastic cron module scattered throughout the codebase meaning they're hard to find and not managed in a consistent manner. cron-master encourages the pattern of storing all jobs in a single location, and ensures they all follow the same pattern. It also adds events to your CronJob instances so you can add generic hooks for logging, detecting errors, and preventing overlapping calls that can cause unpredictable results.

For example, if you have a job that runs every 5 minutes, did you remember to ensure that next time it runs the previous run has completed!? cron-master removes the need to check for that case, it simply won't let the same job run again until the currently running call has completed.

Features

  • Prevents a the same CronJob running more than once concurrently.
  • Provides a structured way to create a manage jobs.
  • Enables jobs to emit and receive events, also provides useful default events.
  • Automatically computes time taken by each CronJob to complete.
  • Provides an error and/or result from each job via an event and/or callback.

Install

Seemples!

npm install cron-master --save

API

module.getInstance()

Factory function that returns manager instances.

const cmaster = require('cron-master');
 
// This is our instance
const instance = cmaster.getInstance()

instance.loadJobs(absolutePath, callback)

Loads all jobs contained in the specified folder, and place them under the managment of cron-master. Each file in the folder must have module.exports set to an instance of CronMasterJob.

As of version 0.2.0 the jobs are not cached. This means if you call load jobs a second time with the same jobs, a new instance of the file containing the job will be loaded. This means the require.cache for the specifc cron files your loading is deleted to prevent conflicts

The callback should be of the format function(err, jobs). jobs will be an Array of the managed jobs that were loaded.

const path = require('path')
const cmaster = require('cron-master');
const manager = cmaster()
 
instance.loadJobs(path.join(__dirname, '../', 'my-jobs'), function (err, jobs) {
  if (err) {
    console.error('Failed to load jobs!');
  } else {
    console.log('Loaded %d jobs!', jobs.length);
  }
});

instance.hasRunningJobs()

Returns a boolean indicating if any jobs are currently running.

instance.getJobs()

Get an Array containing all currently loaded jobs.

instance.getRunningJobs()

Get an Array containing all currently running jobs.

instance.startJobs(callback)

Starts all jobs that are being managed so that they will be run at the time specified by their cron tab. Internally this will call the cron module start function for each job.

instance.stopJobs(callback)

Stops all jobs being managed so they will no longer execute at the time specified by their cron tab. If any jobs are currently in the middle of a tick callback won't fire until they're all complete. Internally this will call the cron module stop function for each job. You can short circuit your job function to stop early by by using the STOP_REQUESTED event, examples below.

module.EVENTS

A map of the event names, helps avoid spelling errors etc. Events:

  • TICK_STARTED - 'tick-started',
  • START_REQUESTED - 'start-requested',
  • TICK_COMPLETE - 'tick-complete',
  • STOP_REQUESTED - 'stop-requested',
  • STOPPED - 'stopped',
  • TIME_WARNING - 'time-warning',
  • OVERLAPPING_CALL - 'overlapping-call'

Usage examples are included below.

module.CronMasterJob

This is a replacement for cron.CronJob (the cron module from npm) that you usually use. It requires parameters to be passed in an Object.

The function you usually pass as the onTick parameter the cron module doesn't take a callback, but when using cron-master you must accept a callback into your cron onTick function as shown below.

Each job exposes the following functions for direct use if required:

instance.start([callback])

Starts the job so that it will run at the specified time(s).

instance.forceRun([callback])

Force the job to run immediately even if already running.

instance.run([callback])

Run the job if it is not currently in progress.

instance.stop([callback])

Stop the job so that it will not run at the specified time(s).

Here's a trivial example of creating a CronMasterJob:

 
var CronMasterJob = require('cron-master').CronMasterJob;
 
module.exports = new CronMasterJob({
 
  // Optional. Used to determine when to trigger the 'time-warning'. Fires after
  // the provided number of milliseconds (e.g 2 minutes in the case below) has
  // passed if the job has not called the done callback
  timeThreshold: 2 * 60 * 1000,
 
  // Optional. Can be used to add useful meta data for a job
  meta: {
    name: 'Test Job'
  },
 
  // Just the usual params that you pass to the "cron" module!
  cronParams: {
    cronTime: '* * * * * *',
    onTick: function (job, done) {
      console.log('Running job!');
      done(null, 'ok');
    }
  }
 
});

Examples

Basic Job

 
var CronMasterJob = require('cron-master').CronMasterJob;
 
module.exports = new CronMasterJob({
  // The usual params that you pass to the "cron" module go here
  cronParams: {
    cronTime: '* * * * * *',
    onTick: function (job, done) {
      console.log('running job');
      done(null, 'result');
    }
  }
});
 

Adding Events to Jobs

var path = require('path')
  , cmaster = require('cron-master');
 
cmaster.loadJobs(path.join(__dirname, '../', 'my-jobs'), function (err, jobs) {
  if (err) {
    console.error('Failed to load jobs!');
  } else {
    jobs.forEach(function (job) {
      // Using event map for name.
      // Log output when the job is about to run.
      job.on(cmaster.EVENTS.TICK_STARTED, function () {
        console.log('Job tick starting!');
      });
 
 
      // Using String for event name.
      // Log output when the job has complete.
      job.on('tick-complete', function (err, res, time) {
        console.log('Job tick complete in %d!', time);
        if (err) {
          console.error('Error running job %s: %s', job.meta.name, err);
        } else {
          console.log('Job complete. Result: %s', res);
        }
      });
 
      job.on(events.TIME_WARNING, function () {
        console.log('Job has %s exceeded expected run time!', job.meta.name);
      });
 
      job.on('overlapping-call', function () {
        console.log(
          'Job %s attempting to run before previous tick is complete!',
          job.meta.name
        );
      });
    });
  }
});

Advanved Job with Short Circuit

The job below runs every 2 minutes. Interestingly however, it binds a one time event listener to see if the job was requested to stop. If so it will prevent further execution and simply skip the business logic.

 
const CronMasterJob = require('cron-master').CronMasterJob  
const async = require('async')
const db = require('lib/db-wrapper')
 
/**
 * Function to call for this cron job.
 * @param  {CronMaster}   job     Reference to the job itself, use for events.
 * @param  {Function}     done    Used to signal the job is finished.
 */
function cronFn (job, done) {
  var stopped = false;
 
  // Let's use the job events to allow this job to be stopped mid process!
  job.once('stop-requested', stopListener);
 
  function stopListener () {
    stopped = true;
  }
 
  function letterProcessor (letter, next) {
    if (!stopped) {
      // Do nothing, just skip since a stop was requested
      next();
    } else {
      db.insert(letter, next);
    }
  }
 
  async.eachSeries(['a', 'b', 'c'], letterProcessor, function (err) {
    // Remove event bindings to prevent memory leaks!
    job.removeListener('stop-requested', stopListener);
 
    // Pass the result to the CronMasterJob callback
    done(err);
  });
}
 
module.exports = new CronMasterJob({
 
  // Optional. Used to determine when to trigger the 'time-warning'. Fires after
  // the provided number of milliseconds (e.g 2 minutes in the case below) has
  // passed if the job has not called the done callback
  timeThreshold: (2 * 60 * 1000),
 
  // Just the usual params that you pass to the "cron" module!
  cronParams: {
    cronTime: '00 */2 * * * *',
    onTick: cronFn
  }
 
});

Changelog

1.0.1

  • Remove EVENTS from typings
  • Fix return type for loadJobs in typings
  • Fix CronMasterJobOptions to accept meta and cronParams

1.0.0

  • Changed public interface
  • Added TypeScript typings
  • Updated tests to use Jest
  • Added coverage
  • Added Synk
  • Added StandardJS formatting

0.3.0

  • Renamed the forceRun function to run to reflect it's true behaviour
  • Added run function that will only run a job if it is not already running

0.2.0

  • Delete require cache for each job loaded
  • Add function hasRunningJobs()
  • Add function getJobs()
  • Add function getRunningJobs()
  • Improve test cases

0.1.0

  • Initial release

Package Sidebar

Install

npm i cron-master

Weekly Downloads

36

Version

1.0.1

License

MIT

Last publish

Collaborators

  • evanshortiss