Calling promise-driven functions regularly with setInterval and operating over its results - javascript

My goal is to make a video playlist generator that runs at specific times, along with a running clock and many other things that run periodically, but I'm stuck at the part where it generates the playlist at the times I define.
I'm using FileHound to walk a folder, which works whenever I call the find() method, but as it's Promise driven, its results aren't readily available to be used by the following console.log(), for example. Same thing happens with MediaInfo(), but this time I was able to work around it with async/await, which as far as I know is the actual way to use a Promise based function.
Now, as far as I can understand, the .each() and .then() method chaining are the ways to use the results from a Promise driven function, but that would quickly result in code repetition for every time I want to do the same thing in different places.
All being said, I think I'm off my track by very far, and despite my efforts I can't seem to find a clear way to achieve what I want, so I'm asking for help. This is the code I have so far, and I'm using the following npm packages:
node-mediainfo
filehound
moment
const MediaInfo = require("node-mediainfo");
const Path = require("path");
const FileHound = require("filehound");
const Moment = require("moment");
const START_TIME = Moment();
const END_TIME = null;
const VIDEO_PATH = "videos";
let files = FileHound.create()
.path(VIDEO_PATH)
.depth(0);
let playlist = [];
let start_time = START_TIME.add(10, "seconds");
const ArrayShuffle = (array) => {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
};
const MakePlaylist = async (file) => {
let fileinfo = await MediaInfo(Path.resolve(file));
let duration = fileinfo.media.track[0].Duration;
let title = fileinfo.media.track[0].Title || Path.basename(file, Path.extname(file));
let artist = fileinfo.media.track[0].Performer || null;
playlist.push({
path : Path.resolve(file),
title : title,
artist : artist,
duration: duration
});
};
console.log(start_time.toLocaleString());
/* Main Loop */
setInterval(() => {
if (Moment().isSame(start_time)) {
console.log("First Run");
files.find().each(MakePlaylist).then(ArrayShuffle);
console.log(playlist);
}
if (Moment().isSame(Moment(start_time).add(30, "seconds"))) {
console.log("Second Run");
playlist = [];
files.find().each(MakePlaylist).then(ArrayShuffle);
console.log(playlist);
}
}, 1);

I would be tempted to put your logic into an async function, making use of await. and then simply recall your function later, instead of trying to do it in setTimeout
Assuming files.find() returns a promise....
const GeneratePlaylist = async () => {
var files = await files.find();
var playlist = [];
for(var i=0;i<files.length;i++){
playlist.push(await MakePlaylist(files[i]));
}
ArrayShuffle(playlist);
return playlist;
}
Then you can use that from another async function, and you can recall it 1ms later with setTimeout (Note, you should make MakePlaylist return the new item, not push to a global array):
const doMyThing = async (){
if (Moment().isSame(start_time)) {
console.log("First Run");
playlist = await GeneratePlaylist();
console.log(playlist);
}
if (Moment().isSame(Moment(start_time).add(30, "seconds"))) {
console.log("Second Run");
playlist = [];
playlist = await GeneratePlaylist();
console.log(playlist);
}
setTimeout(doMyThing,1);
}
Below is a working example, where I've just faked up some of your functionality using Promises to simulate asynchronous work like finding files and loading the media info:
const ArrayShuffle = (array) => {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
};
const MakePlaylist = async (file) => {
let fileinfo = await new Promise(resolve => setTimeout(() => resolve({media:{track:[{Duration:1,Title:"Title",Performer:"Foo"}]}}),1000));
let duration = fileinfo.media.track[0].Duration;
let title = fileinfo.media.track[0].Title || Path.basename(file, Path.extname(file));
let artist = fileinfo.media.track[0].Performer || null;
return{
path : file,
title : title,
artist : artist,
duration: duration
};
};
const findFileMockup = () => {
return new Promise(resolve => setTimeout(() => {
resolve(["file1.txt","file2.txt","file3.txt"]);
},500));
}
const GeneratePlaylist = async () => {
var files = await findFileMockup(); // await files.find() in your code
var playlist = [];
for(var i=0;i<files.length;i++){
playlist.push(await MakePlaylist(files[i]));
}
ArrayShuffle(playlist);
return playlist;
}
const test = async () => {
var playlist = await GeneratePlaylist();
console.log(playlist);
}
test();

Related

rss-parser reposts same every time

I wanted some help on the rss-parser, So I wanted to make the command send the message to the channel only once, but at a certain time it keeps Re reposting the same thing every time and I am not able to resolve this.
(async function main() {
// Make a new RSS Parser
const parser = new Parser();
// Get all the items in the RSS feed
const feed = await parser.parseURL("https://imaginescan.com.br/feed"); // https://www.reddit.com/.rss
let items = [];
// Clean up the string and replace reserved characters
const fileName = `${feed.title.replace(/\s+/g, "-").replace(/[/\\?%*:|"<>]/g, '').toLowerCase()}.json`;
if (fs.existsSync(fileName)) {
items = require(`./${fileName}`);
}
// Add the items to the items array
await Promise.all(feed.items.map(async (currentItem) => {
// Add a new item if it doesn't already exist
if (items.filter((item) => isEquivalent(item, currentItem)).length <= 0) {
items.push(currentItem);
}
}));
// Save the file
fs.writeFileSync(fileName, JSON.stringify(items));
feed.items.reverse().forEach(async (item) => {
const channel = client.channels.cache.get('980990991865626634');
let embed = new Discord.MessageEmbed()
.setTitle("New Cap")
.setDescription(item.title)
.setColor("PURPLE")
.setImage()
channel.send(embed)
})
})();
function isEquivalent(a, b) {
// Create arrays of property names
let aProps = Object.getOwnPropertyNames(a);
let bProps = Object.getOwnPropertyNames(b);
// if number of properties is different, objects are not equivalent
if (aProps.length != bProps.length) {
return false;
}
for (let i = 0; i < aProps.length; i++) {
let propName = aProps[i];
// if values of same property are not equal, objects are not equivalent
if (a[propName] !== b[propName]) {
return false;
}
}
// if we made it this far, objects are considered equivalent
return true;
}
I'm new to this, so I wanted some help with this problem.

Trying to use replace() method on files by passing the string not working - JS

I'm trying to replace a string, but when i try to do it on a file, it does not work. Or only the last file will work.
Main Code (inside a class method):
const funcs = fs.readdirSync(path.join(__dirname, "../funcs")).filter(file => file.endsWith('.js'));
this.functions = this.code.split("$");
const functions = this.functions
for (const func of funcs) {
for (let x = functions.length - 1; x > 0; x--) {
let i = 0;
const res = await require(`../funcs/${func}`)(client, this.code.toString(), this._author);
console.log(x);
console.log(res);
console.log(functions);
return res;
i++;
}
}
ping.js:
const ping = (client, code, author) => {
const result = code.split("$[ping]").join(client.ws.ping)
return result;
}
module.exports = ping;
messageAuthorTag.js:
const Util = require('../utils/util');
const messageAuthorTag = (client, code, author) => {
if (code === null) return;
const res = code.split("$[message.author.tag]").join(`${author.tag}`);
return res;
}
module.exports = messageAuthorTag;
Using it : "ping: $[ping] author tag: $[message.author.tag]"
Output:
ping: $[ping] author tag: Example#0001
Your issue is that you return res in the middle of a for loop. This means it won't do anything else in the function and will skip the rest of the loops (like running the other functions!)
As an additional note, you could also remove the let i = 0 and i++ since that doesn't seem to be used for anything.

Issues with Array Variable

app.get("/indsalesx/:store/:mm", (req, res) => {
connect();
let ddd = [];
let staffarray = [{}];
let store = req.params.store;
let mm = req.params.mm;
const SP = mongoose.model(`sales${store}`, Sales);
let num = stafflist[store].length - 1;
for (i = 0; i <= num; i++) {
let staffname = stafflist[store][i];
let calc = 0;
SP.find(
{ v_salesperson: stafflist[store][i], v_month: mm },
"v_amount",
(err, doc) => {
let t = doc.length - 1;
doc.map((res) => {
calc = calc + res.v_amount;
});
ddd.name = staffname;
ddd.amount = calc;
staffarray.push(ddd);
}
);
}
console.log(staffarray);
});
The issue I have is: Why is staffarray returning an empty array? staffarray was declared as an empty array of objects, and in a loop function, objects were pushed to to array. But when I console.log(staffarray), it returns the empty array of objects declared initially.
Any help on what to do?
When using find(), you can use 2 approaches.
Pass a callback function
await the function to execute and return the results.
It appears that you used the first approach which means that you are passing a callback into the find() method which handles the result once received.
The console.log() code line will execute before the result will return since it's the next line to execute after the for loop.
So, let's go through what it happening here:
Javascript is executing the find() code line.
That line of code is being placed in the web API which are the pieces of the browser in which concurrency kicks in and makes the call to the server for us.
The console.log() line is being executed with an empty array (since the results haven't been received yet.
After some time, results came back and the callback is being set in the callback queue.
The JS event loop takes the callback from the callback queue and executes it.
This is part of the javascript event loop. you could read more about this here
Mongoose documentation: Model.find()
you can use for of with async/await instead of for
app.get("/indsalesx/:store/:mm", async(req, res) => {
connect();
let ddd = [];
let staffarray = [{}];
let store = req.params.store;
let mm = req.params.mm;
const SP = mongoose.model(`sales${store}`, Sales);
let num = stafflist[store].length - 1;
var list = Array.from(Array(num).keys());
for (let i of list) {
let staffname = stafflist[store][i];
let calc = 0;
let doc = await SP.find(
{ v_salesperson: stafflist[store][i], v_month: mm },
"v_amount"
);
let t = doc.length - 1;
doc.map((res) => {
calc = calc + res.v_amount;
});
ddd.name = staffname;
ddd.amount = calc;
staffarray.push(ddd);
}
console.log(staffarray);
});
I have been able to solve it, all I needed was proper structuring with the async and await statements.
app.get("/indsalesx/:store/:mm", async (req, res) => {
connect();
let ddd = {};
let staffarray = [];
let store = req.params.store;
let mm = req.params.mm;
const SP = mongoose.model(`sales${store}`, Sales);
let num = stafflist[store].length - 1;
for (i = 0; i <= num; i++) {
let staffname = stafflist[store][i];
let calc = 0;
await SP.find(
{ v_salesperson: stafflist[store][i], v_month: mm },
"v_amount",
(err, doc) => {
let t = doc.length - 1;
doc.map((res) => {
calc = calc + res.v_amount;
});
staffarray.push({ name: staffname, amount: calc });
}
);
}
console.log(staffarray);
res.send({ data: staffarray });
});

Javascript cancel async for loop

I found this code in a project:
const fn = async () => {
let x = 0;
for(let i = 0; i < 50; i++){
const res = await api.call(i);
if(res.someProp) x++;
}
return x;
}
I want to be able to stop it mid way, so that if I call it again, it will start from scratch and discard the previous call results. To avoid making two sets of requests at the same time.
This should do:
let token;
const fn = async () => {
const my = token = Symbol();
let x = 0;
for(let i = 0; i < 50 && my == token; i++){
const res = await api.call(i);
if(res.someProp) x++;
}
return x;
}
While there still can be some overlap between the calls, any previous loops will break their iteration as soon as the next fn() call is started.
You can use any technique of using an external flag variable to break the loop.
As a workaround you can try to use a custom Promise class (Live demo):
import CPromise from "c-promise2";
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
async function api(i) {
console.log(`Async API call [${i}]`);
await delay(100);
return {};
}
const fn = () =>
CPromise.from(function* () {
let x = 0;
for (let i = 0; i < 50; i++) {
const res = yield api.call(i);
if (res.someProp) x++;
}
return x;
});
const cancelablePromise = fn().then(
() => console.log("Done"),
(err) => console.log(`Fail: ${err}`) // Fail: CanceledError: canceled
);
setTimeout(() => {
cancelablePromise.cancel(); // abort the async sequence (loop) after 3500ms
}, 3500);

Creating a Timestamped Object Array for Sampling Data in Javascript?

Goal is to push sampled data, as an object, onto an array, at a periodic interval and wait to log the new array out to the console once it is finalized.
I'm new to JS, so take it easy ;). I am likely making this more complicated than it needs to be. Thought it would be as simple as a setTimeout() in a for loop.
I have been able to generate the array two different ways, using IIFE with a setTimeout() also the setInterval() below. Not sure how to get the async await function working with an array push() method querying length. Maybe this is not a good approach?
class Sample {
constructor(tag, timeStamp) {
this.tag = tag;
this.timeStamp = Date.now();
}
}
function arrayGenerator(tag){
return sampleArr.push(new Sample(tag));
};
function setIntSample(callback, delay, iterations) {
var i = 0;
var intervalID = setInterval(function () {
callback(i);
if (++i === iterations) {
clearInterval(intervalID);
}
}, delay);
};
Above seems to work console.log()-ing the array as it is generated in the arrayGenerator() function. Below, no dice
function resolveAfterArrGeneration(){
return new Promise(resolve => {
arrLength = setIntSample(i => {arrayGenerator(i)}, 3000, 5)
if (arrLength === 5) {resolve();}
});
}
async function ans() {
var answer = await resolveAfterArrGeneration();
console.log(sampleArr);
}
ans();
The basic idea is to return a promise and resolve the promise when the setInterval has run enough iterations. You can do that in a single function with something like this (with extra console.logs to show the process):
class Sample {
constructor(tag, timeStamp) {
this.tag = tag;
this.timeStamp = Date.now();
}
}
function makeSamples(iterations, delay){
let samples = [], i = 0;
return new Promise(resolve => {
let intervalID = setInterval(function () {
console.log("pushing new sample")
samples.push(new Sample('tag: ' + i));
if (++i === iterations) {
console.log("finished resolving")
clearInterval(intervalID);
resolve(samples)
}
}, delay);
})
}
makeSamples(5, 1000).then(console.log)
I would isolate the delay part (the asynchronous) part and create a separate, generic function delay() for that. All the rest becomes simple then, using an async function and for loop:
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
class Sample {
constructor(tag, timeStamp) {
this.tag = tag;
this.timeStamp = Date.now();
}
}
async function setIntSample(callback, ms, iterations) {
const arr = [];
for (let i = 0; i < iterations; i++) {
if (i) await delay(ms); // don't delay first time
arr.push(callback(i));
}
return arr;
}
const newSample = (tag) => new Sample(tag)
console.log("wait for it....");
setIntSample(newSample, 1000, 5).then(console.log);
Another way I just got working with a generator function
function* simpleGenerator(){
var index = 0;
while (true)
yield {tag: index++, time: Date.now()}
}
var gen = simpleGenerator();
..with the corresponding push
arr.push(gen.next().value);

Categories

Resources