Skip to content

Commit

Permalink
feat(schedule): Added interval scheduling capability
Browse files Browse the repository at this point in the history
  • Loading branch information
Wes committed Oct 20, 2017
1 parent 761a55f commit 690037b
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 13 deletions.
97 changes: 95 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
## Purpose
- Register a Job and set its maximum concurrency;
- Be able to trigger the job manually many times with the gurarantee that the set concurrency will be respected;
- Be able to run a job at an interval with the guarantee muiltiple runs wont overlap.
- (Future) Be able to ensure these guarantees across multiple node servers by coordinating runs(redis? mysql? websockets?)

## Instalation
```
npm i tnt-scheduler --save
```

## See it working
[Run it in Runkit](https://npm.runkit.com/tnt-scheduler)

## Usage:

Creating a new scheduler:
Expand All @@ -29,7 +38,7 @@ try {
}
```

Running your job:
Triggering your job manualy:
```javascript
try {
sch.startJob('my_job');
Expand All @@ -47,7 +56,91 @@ try {
}
```

Full example:
Scheduling your job at an interval:
```javascript
try {
sch.scheduleJob('my_job', 1000);
} catch (error) {
console.log(error.message);
}
```

Scheduling your job at an interval with arguments:
```javascript
try {
sch.scheduleJob('my_job', 1000, 'arg1', 'arg2', 'argN');
} catch (error) {
console.log(error.message);
}
```

Canceling a scheduled job:
```javascript
try {
sch.clearSchedule('my_job');
} catch (error) {
console.log(error.message);
}
```

Complete example: Scheduling a job.
```javascript
const Scheduler = require('tnt-scheduler');
let sch = new Scheduler();

// This is only necessary if you are using scheduleJob
sch.on('error', error => {
console.log(error.message);
});

try {
sch.createJob('my_job', myJob);
} catch (error) {
console.log(error.message);
}

sch.on('running::my_job', promise => {
console.log('running my_job');
promise.then(resolution=>{
console.log('resolved with', resolution);
}, error=>{
console.log('rejected with', error);
})
});

try {
sch.scheduleJob('my_job', 1000, 'arg1', 2, {arg: 3});
} catch (error) {
console.log(error.message);
}

// myJob is a simple function that returns a promise that randomly
//resolves or rejects after 3000 ms
function myJob(...args) {
// returns our promise
return new Promise(function (resolve, reject) {
// Waits 3 seconds
setTimeout(() => {
// Roll the dice
let odds = Math.round(Math.random());
if (odds) {
// Resolves if 1;
resolve(args);
} else {
// Rejects if 0
reject('Boom!');
}
}, 3000);
})
}

// Clears the schedule after 10 seconds
setTimeout(() => {
sch.clearSchedule('my_job');
}, 10000)
```

Complete example: Triggering a job
```javascript
const Scheduler = require('tnt-scheduler');
let sch = new Scheduler();
Expand Down
53 changes: 53 additions & 0 deletions example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const Scheduler = require('tnt-scheduler');
let sch = new Scheduler();

// This is only necessary if you are using scheduleJob
sch.on('error', error => {
console.log(error.message);
});

try {
sch.createJob('my_job', myJob);
} catch (error) {
console.log(error.message);
}

sch.on('running::my_job', promise => {
console.log('running my_job');
promise.then(resolution=>{
console.log('resolved with', resolution);
}, error=>{
console.log('rejected with', error);
})
});

try {
sch.scheduleJob('my_job', 1000, 'arg1', 2, {arg: 3});
} catch (error) {
console.log(error.message);
}

// myJob is a simple function that returns a promise that randomly
//resolves or rejects after 3000 ms
function myJob(...args) {
// returns our promise
return new Promise(function (resolve, reject) {
// Waits 3 seconds
setTimeout(() => {
// Roll the dice
let odds = Math.round(Math.random());
if (odds) {
// Resolves if 1;
resolve(args);
} else {
// Rejects if 0
reject('Boom!');
}
}, 3000);
})
}

// Clears the schedule after 10 seconds
setTimeout(() => {
sch.clearSchedule('my_job');
}, 10000)
56 changes: 53 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
class Scheduler {
constructor(projects) {
const EventEmitter = require('events');

class Scheduler extends EventEmitter {
constructor() {
super();
this.jobs = {};
this.schedules = {};
}

/**
Expand Down Expand Up @@ -29,7 +33,7 @@ class Scheduler {
* @param {string} name The name of your Job
* @throws {Error} Throws an Error if there is no Job registered by that name
*/
removeJob(name){
removeJob(name) {
if (!this.jobs[name]) {
throw new Error(`No job named ${name} found`);
} else {
Expand Down Expand Up @@ -58,8 +62,54 @@ class Scheduler {
job.current++;
let promise = job.executor(...args);
promise.then(() => { job.current-- }, () => { job.current-- });
this.emit(`running::${name}`, promise);
return promise;
}

/**
* Run your Job periodically
*
* @param {string} name The name of your Job
* @param {number} interval The interval between run attempts
* @param {...*} args The arguments that will be passed to your Job executor
*/
scheduleJob(name, interval, ...args) {
if (!this.jobs[name]) {
throw new Error(`No job named ${name} found`);
}

if (this.schedules[name]) {
throw new Error(`Job ${name} has already been scheduled at a ${this.schedules[name].interval} ms interval`);
}

if (!interval) {
throw new Error(`Param interval must be a number > 0`);
}

this.schedules[name] = {};
this.schedules[name].interval = interval;
this.schedules[name].intervalId = setInterval(() => {
try {
this.startJob(name, ...args);
} catch (error) {
this.emit('error', error);
}
}, interval);
}

/**
* Stop Job's periodic runs
*
* @param {string} name The name of your Job
*/
clearSchedule(name) {
if (!this.schedules[name]) {
throw new Error(`There is no current schedule for job ${name}`);
}

clearInterval(this.schedules[name].intervalId);
delete this.schedules[name];
}
}

module.exports = Scheduler;
21 changes: 13 additions & 8 deletions test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const Scheduler = require('./index');
const Scheduler = require('tnt-scheduler');
let sch = new Scheduler();

try {
// This will succeed as there is no job by this name
sch.createJob('my_job', myJob);
Expand All @@ -10,10 +10,10 @@ try {
// This will print 'Name my_job already taken' as the second call to createJob throws
console.log(error.message);
}

// This will start my_job once per second, since the job takes 3 seconds
//to run it will fail 2 out of 3 times
setInterval(() => {
let interval = setInterval(() => {
try {
// Starting the job with two arguments
sch.startJob('my_job', 'arg1', 12345);
Expand All @@ -22,7 +22,7 @@ setInterval(() => {
console.log(error.message);
}
}, 1000);

// The code above will log something like this to the console:
//running myJob
//Job my_job is at max concurrency level of 1
Expand All @@ -33,14 +33,14 @@ setInterval(() => {
//Job my_job is at max concurrency level of 1
//rejecting arg1 12345
// ...

// myJob is a simple function that returns a promise that randomly resolves or rejects
function myJob(arg1, arg2) {
// Will print if the job is started sucessfully
console.log('running myJob');
// returns our promise
return new Promise(function (resolve, reject) {

// Waits 3 seconds
setTimeout(() => {
// Roll the dice
Expand All @@ -56,4 +56,9 @@ function myJob(arg1, arg2) {
}
}, 3000);
})
}
}

// Clears the schedule after 10 seconds
setTimeout(()=>{
clearInterval(interval);
},10000)
52 changes: 52 additions & 0 deletions testSchedule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const Scheduler = require('./index');
let sch = new Scheduler();

sch.on('error', error => {
console.log(error.message);
});

try {
sch.createJob('my_job', myJob);
} catch (error) {
console.log(error.message);
}

sch.on('running::my_job', promise => {
console.log('running my_job');
promise.then(resolution=>{
console.log('resolved with', resolution);
}, error=>{
console.log('rejected with', error);
})
});

try {
sch.scheduleJob('my_job', 1000, 'arg1', 2, {arg: 3});
} catch (error) {
console.log(error.message);
}

// myJob is a simple function that returns a promise that randomly
//resolves or rejects after 3000 ms
function myJob(...args) {
// returns our promise
return new Promise(function (resolve, reject) {
// Waits 3 seconds
setTimeout(() => {
// Roll the dice
let odds = Math.round(Math.random());
if (odds) {
// Resolves if 1;
resolve(args);
} else {
// Rejects if 0
reject('Boom!');
}
}, 3000);
})
}

// Clears the schedule after 10 seconds
setTimeout(() => {
sch.clearSchedule('my_job');
}, 10000)

0 comments on commit 690037b

Please sign in to comment.