schedule route or controller - node.js

i have a controller
exports.updateDaily = async (req, res) => {
try {
const updateDaily = await transaction.decrement(
{
remainActive: 1,
},
{
where: {
remainActive: { [Op.gte]: 1 },
},
}
);
console.log(updateDaily);
res.status(200).send({
status: "Success",
});
} catch (error) {
console.log(error);
res.status(400).send({
status: "Failed",
});
}
};
and route like this
router.patch('/updateDaily', updateDaily)
Using cron-node
let job = new cron.schedule('2 * * * *', () => {
router.patch('/updateDaily', updateDaily);
});
job.start()
Using setIntverval
const scheduller = () => {
return router.patch('/updateDaily', updateDaily);
}
setInterval(scheduller,600000)
how can i make one of them run every 10 minutes? i already try it with node-cron or setInterval but there is nothing happen

You got the concept wrongly. Having a scheduled job means running something periodically which has to be a function that does something. You don't need a router defined in it. Your code does only one thing - schedules a router to be available for the user once the scheduled time happens and redefines it every 10 minutes.
What you need is to create a separate function for the operation and call it on a scheduled time and having a router at this point is extra unless you want to run it when the user sends a request as well.
const myDailyTask = async () => {
await transaction.decrement(
{
remainActive: 1,
},
{
where: {
remainActive: { [Op.gte]: 1 },
},
}
);
};
const id = setInterval(myDailyTask, 600_000);
It should work for the cron job the same way
const job = new cron.schedule('2 * * * *', () => {
myDailyTask();
});
job.start();

Related

running a fastify route periodically (via cron)

I would like to run a particular fastify route periodically. There is fastify-cron but I can't figure out if I can use that to call a route from within fastify. To summarize, given a route as below, I would like https://my/server/greeting to be called every midnight.
fastify.get('/greeting', function (request, reply) {
reply.send({ hello: 'world' })
})
By using that plugin you can use the inject method, used to write tests typically:
import Fastify from 'fastify'
import fastifyCron from 'fastify-cron'
const server = Fastify()
server.get('/greeting', function (request, reply) {
reply.send({ hello: 'world' })
})
server.register(fastifyCron, {
jobs: [
{
cronTime: '0 0 * * *', // Everyday at midnight UTC
onTick: async server => {
try {
const response = await server.inject('/greeting')
console.log(response.json())
} catch (err) { console.error(err) }
}
}
]
})
server.listen(() => {
// By default, jobs are not running at startup
server.cron.startAllJobs()
})
This is needed because you need to generate e request/response object to run your handler.

Asynchronously generate list of reverse geocoded addresses without API rate limiting

I'm generating test/development dummy data with a node script in my Mongo database (using Mongoose) which includes Geolocation coordinates. (sets of lat/lon). Schema follows:
location: {
type: {
type: String,
enum: ["Point"], // 'location.type' must be 'Point'
default: "Point",
},
coordinates: {
type: [Number],
required: true,
},
geocoded: {
type: String, // this has to be done with an external API
},
},
For that reason, I have an external (paid) Reverse Geocoding API which I want/need to call for each document/set of coordinates. The Geocoding API though has a rate limiter so I'm hitting 429 - too many requests. I'm looking for a clean and simple solution to run my requests sequentially and add a throttling/waiting time ( for a specified number of milliseconds ) after each HTTP request.
messageSchema.pre("insertMany", async function save(next, docs) {
docs.map(async (doc) => { // now I understand I should replace map with for ... of or for ... in
[err, response] = await to(
reverseGeocode(
doc.location.coordinates[0],
doc.location.coordinates[1]
)
);
if (err) {
next(err);
}
doc.location.geocoded = response;
});
});
The reverseGeocode signature:
reverseGeocode: (lon, lat) =>
axios({
baseURL: "https://eu1.locationiq.com/",
url: "v1/reverse.php",
params: {
lat,
lon,
key: geocodeKey,
},
}).then((response) => response.data),
I use this library for throttling requests. You simply tell it what the rate limit of the API is, and you can call it as fast as you want and it will automatically space the requests out over time for you.
If you don't want another dependency, then to make your solution work you need to use a for loop. map will always execute as fast as it possibly can.
const wait = (time) => {
return new Promise((resolve) => {
setTimeout(resolve, time);
});
}
messageSchema.pre("insertMany", async function(next, docs) {
for(let i in docs) {
const doc = docs[i];
await wait(3000); // milliseconds to space requests out by.
const response = await reverseGeocode(
doc.location.coordinates[0],
doc.location.coordinates[1]
);
}
console.log(this);
});
There's no need to "fork" the process as nodejs has native async promise support.
The best solution is actually to add delays to your code to match the limit. Assuming this is too cumbersome and the fact that this is only for development purposes here is a quick brute force example:
Note that waiting on a promise does not block the process as synchronous code would.
async function getGeoCode(doc) {
return reverseGeocode(
doc.location.coordinates[0],
doc.location.coordinates[1]
)
}
const randomSleep = [1000, 2000, 3000, 4000, 5000, 6000];
messageSchema.pre("insertMany", async function save(next, docs) {
let sleep = false;
docs.map(async (doc) => {
while (sleep) {
const randomSleep = randomSleep[Math.floor(Math.random() * randomSleep.length)];
await new Promise(function (resolve) {
setTimeout(resolve, 2000 + randomSleep)
});
}
let [err, response] = await getGeoCode(doc);
if (err) {
if (err.statusCode !== 429) {
throw new Error(err);
}
sleep = true;
while (err && err.statusCode === 429) {
const randomSleep = randomSleep[Math.floor(Math.random() * randomSleep.length)];
await new Promise(function (resolve) {
setTimeout(resolve, 2000 + randomSleep)
});
[err, response] = await getGeoCode(doc);
}
sleep = false;
}
});
console.log(this);
});

How to add typing delay in botkit conversations with facebook adapter

We are building a facebook messenger bot with Botkit version 4.
We have tried different ways to add typing delay with the messages but none of them is working properly.
As we are using the conversations we are unable to use this format in middleware
bot.reply(message, { type: "typing" });
So we are using something like the following:
Solution 1
controller.middleware.send.use(async function (bot, message, next) {
if (bot._config && bot._config.activity && bot._config.activity.conversation && bot._config.activity.conversation.id) {
await typingIndicator(bot,bot._config.activity.conversation.id)
setTimeout(async () => { next(); }, 2000);
} else {
next();
}
});
async function typingIndicator(bot, id) {
await bot.api.callAPI('/me/messages', 'post', {
recipient: { id: id }, sender_action: 'typing_on'
})
}
But this implementation is not working as expected.
I have around 200 - 300 threads so I tried adding the delay using convo.before()
But even this is breaking in between
Solution 2
let dialog = new BotkitConversation('dialog', controller);
dialog.addMessage({text: 'Message 1', action: 'thread_2'}, 'thread_1');
dialog.addMessage({text: 'Message 2', action: 'thread_3'}, 'thread_2');
dialog.addMessage({text: 'Message 3', action: 'thread_4'}, 'thread_3');
dialog.addMessage({text: 'Message 4'}, 'thread_4');
for (let key in dialog.script) {
dialog.before(`${key}`, async (convo, bot) => {
return new Promise((resolve, reject) => {
setTimeout(resolve, 2000);
});
});
}
Any help will be appreciated.
I solved it like this now:
async function middlewareTest(bot, message, next) {
if(message.text.length > 0){
let time = message.text.length * 30;
await setTimeout(async ()=> {await next();}, time);
} else {
await next();
}
}
controller.middleware.send.use(middlewareTest);
I check if the messages text that is going to be sent has more than 0 characters. If thats the case I do a timeout.
Hope this helps.

Call external API until request is marked as complete

I have following situation.
I have a service which runs jobs on a remote service and exposes API for calling the same.
I have an array of jobs which needs to be executed on remote server and it works fine, when used Promises.
The flow is as below
Inside main function I get a token. On .then() of the same, I initiate my for loop for jobs and pass the JobID and token. This second function returns me the execution_ID of each job. On the .then() of this second function I pass token and execution_id. This returns me the status of the job.
My problem is; when a job is executed, it send me queued, initiated and completed as status. When the status turns to 'completed' I get results of the job; which I need to display.
I tried using a setTimeOut() inside the last function, but I'm not sure when it will end as each job may take different time.
Is there a way I can call this third function multiple time unless the status changes to 'completed'?
Below is the code
app.get('/ExecuteJobs', function (req, res) {
var commandArraay = ["job1", "job2"]
var sTokenID;
getAuthToken()
.then(function (token) {
commandArraay.forEach(function (element) {
getExecutionID(token, element)
.then(function (executionResult) {
setTimeout(() => {
getExecutionResult(token, executionResult.id, executionResult)
.then(function (updateArray) {
console.log("Final Status " + "ID: " + executionResult.id + " Status: " + executionResult.status);
// if(executionResult.)
})
}, 10000)
})
})
})
res.send('Done');
});
// Function to get the auth token
async function getAuthToken(token) {
return new Promise(function (resolve, reject) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
var pUserID = 'uname'
var pPwd = 'pwd'
performhttpsRequest('/<remote_api>/tokens', 'POST', {
sAuth: "Basic " + new Buffer(pUserID + ":" + pPwd).toString("base64")
}, "0", token,
function (data1) {
sTokenID = data1.token;
resolve(sTokenID);
})
})
}
// Function to post the command and get execution ID
async function getExecutionID(tokenID, command, executionID) {
return new Promise(function (resolve, reject) {
performhttpsRequest('/<remote_api>/executecommand', 'POST', {
command: command
}, "1", tokenID,
function (data1) {
var executionID = data1.execution_id;
resolve(executionID);
})
})
}
// Function to get the execution results for an ID
async function getExecutionResult(tokenID, executionID, result) {
return new Promise(function (resolve, reject) {
performhttpsRequest('/<remote_api>/execution_result/' + executionID, 'GET', {
}, "1", tokenID,
function (result) {
resolve(result.result);
})
})
}
If you absolutely need a result after the enqueued job is settled, then I don't see how you can have any other choice other than retrying to check the status every n-seconds.
Here's how I would do it. A recursive function that retries a request n-times, each time waiting n-seconds:
// Mock request, resolves { status: 'completed' } 20% of the time.
const request = () => {
return new Promise(resolve => {
Math.random() < 0.2
? resolve({ status: 'completed', foo: 'bar' })
: resolve({ status: 'pending' })
})
}
const retryToCompletion = async ({ wait, retries }) => {
console.log('Retries left:', retries)
const result = await new Promise((resolve, reject) => {
setTimeout(() => request().then(resolve).catch(reject), wait)
})
if (result.status === 'completed')
return result
if (retries)
return retryToCompletion({ wait, retries: --retries })
throw new Error('Retry attempts exhausted')
}
retryToCompletion({ wait: 1000, retries: 5 })
.then(console.log)
.catch(console.error)
That being said, some API's that work with BASE queues offer a handle to a WebSocket connection that notifies when a job is completed. If the API offers that, then you should ditch retrying and use the completed notification instead.
making GET calls until completion
/**
* cbk be called when..complete execution ok
*/
function completeExecution(tokenID, executionID, result, callPeriod, cbk){
return getExecutionResult(tokenID, executionID, result).then(res=>{
if(res.completed){
return cbk(null, res)
}
setTimeout(function(){
return completeExecution(cbk); //reenqueue
}, callPeriod)
}).catch(cbk) //up to you to abort or insist
}
Then you can promisify completeExecution (with util.promisify or by yourself)

NodeJS cron job - Mongoose won't execute a .find on a model via a function called in a cron tab

I'm having this weird situation where my Cron Job is successfully executing a function that returns a promise, however, when it tries to execute a .find() on the Model, it never actually executes. I have this same function used elsewhere in my app and is called via an API call and returns no problem. Is there something I'm missing?
Here is my cron script:
var CronJob = require('node-cron');
var TradeService = require('../services/TradeService');
// Setup the cron job to fire every second
CronJob.schedule('* * * * * *', function() {
console.log('You will see this message every second');
TradeService.executePendingTrades();
}, null, true, 'America/Los_Angeles');
Here are the related functions that get called:
exports.executePendingTrades = () => {
// Get all pending trades
exports.getPendingTrades().then(results => {
console.log('results', results); // This never fires
})
}
exports.getPendingTrades = () => {
return new Promise((resolve, reject) => {
Trades.find({})
.where('is_canceled').equals('false')
.where('is_completed').equals('false')
.sort('-created_at')
.exec( (err, payload) => {
if (err) {
return reject(err); // This never fires
}
return resolve(payload); // This never fires
})
});
}
This is a shot in the dark, but make sure you are starting a database connection in your CRON job. Otherwise you won't be able to execute any queries.

Resources