Make a loop stop on variable being false - javascript

I'm trying to make a discord bot that says a message every 5 seconds when a condition or command is executed, and stop that loop when another one is executed. I have simple code and I'm not sure why its not working, I have just make it log to console for testing right now.
I have tried while loop and for loop, setinterval and they just won't stop even when I set the var to false.
var c = 0;
if (message.content == prefix + 'test') {
message.delete();
c = 1;
while (c = 1) {
setInterval(function () {
console.log(c);
}, 5000)
}
}
if (message.content == prefix + 'stoptest') {
message.delete();
c = 2;
}
if (message.content == prefix + 'check') {
message.delete();
console.log(c);
}
I'm confused why when I do the stoptest command and set the var c to 2 it still continues to log in console saying that c = 1.

when you write :
while (c = 1) {
setInterval(function () {
console.log(c);
}, 5000)
}
c = 1 is not a condition, check operator in JS ==
setInterval() is already designed to call something indefinitely so don't nest it in a while loop
if you want to write a loop that can be stopped :
let flag = true;
while(flag) {
//do something
if(anythingYouChooseHappens) {
flag = false; // that stops the loop
// or if don't want to use a variable you can just write `break` in this closure
}
}
I think while loop is not the best solution here so you can start with :
let myInterval;
myInterval = setInterval(function() {
console.log(c);
if(anythingYouChooseHappens) {
clearInterval(myInterval); //stop the 'loop'
}
}, 5000);

The reason this is not working is because the while loop is happening synchronously, meaning the code following it will never get a change to execute as the while loop will never actually exit.
If you simply want to keep posting messages until a certain event fires, a loop is not necessary. Instead, you can try:
var interval;
if (message.content == prefix + 'test') {
interval = setInterval(function () {
// Post your messages to Discord here
}, 5000)
}
if (message.content == prefix + 'stoptest' && interval) {
clearInterval(interval);
}
Here, we set an interval that starts when the message content is prefix + 'test', and we clear that interval whenever the content is prefix + 'stoptest'. This will keep your code asynchronous, meaning the rest of your code will get a chance to execute once the interval has been started.

You can try this:
var a;
if(message.content == prefix + 'ckeck'){
a = setInterval(()=>{
if(message.content, == prefix + 'stop'){
clearInterval(a);
}
},5000);

Related

SOLVED: Updating async functions between modules

I have been working on a simple loading animation for backend node.js projects and I ran into a problem after trying to modularize it (so that I can just require() it in other projects)
When run, this loading bar creates a spinner out of braille characters in the console and tries to take three inputs (which used to be global variables before I tried to encapsulate the function, however, when that didn't work I added them to the exported variables in module.exports):
totalSteps: the total # of steps the given function has.
currentStep: the current step the function is on.
statusMessage: the message to display while the spinner loads.
every 100ms, the spinner function updates the frame on the animation which includes the spinner, a percentage of complete tasks, the fraction of done vs not done steps, the message, and three animated trailing dots.
for some reason I can not get this function to work now that I have put it in its own module. It does not wait for the provided function to end before it prints its "Done!" message. however, the variables do seem to be shared between the two scripts, as the final "Done!" message includes the message, and the step variables in it.
Here's my code so far for the loading.js file:
module.exports = {
currentStep : 0,
totalSteps : 0,
statusMessage : '',
spinner : async function (func) {
// declare array of frames for the animation
const spinnerAnimation = ['[⠋]', '[⠙]', '[⠹]', '[⠸]', '[⠼]', '[⠴]', '[⠦]', '[⠧]', '[⠇]', '[⠏]'];
const dots = [' ', ' ', ' ', '.', '.', '.', '.. ', '.. ', '.. ', '...', '...', '...'];
let spinnerCounter = 0;
let dotCounter = 0;
// overwrite the line with the new frame every 100ms
const intervalId = setInterval(() => {
spinnerCounter = (spinnerCounter + 1) % spinnerAnimation.length;
dotCounter = (dotCounter + 1) % dots.length;
if (this.statusMessage == '') this.statusMessage = 'Loading';
if (this.currentStep <= this.totalSteps && this.currentStep >= 0 && this.totalSteps >= 1) {
// if the variables used for calculating the % value are in the correct range
// show the % and the # of steps completed out of the total
process.stdout.write(`\r\x1b[32m\x1b[?25l${spinnerAnimation[spinnerCounter]} ${Math.round(this.currentStep / this.totalSteps * 10000)/100}% (Step: ${this.currentStep}/${this.totalSteps} complete)\x1b[0m - ${this.statusMessage}${dots[dotCounter]} `);
} else {
// if not, just dont show them. that way if it fails, the animation still shows but you dont get any weird errors (or NaN/undefined placeholders)
process.stdout.write(`\r\x1b[32m\x1b[?25l${spinnerAnimation[spinnerCounter]}\x1b[0m - ${this.statusMessage}${dots[dotCounter]} `);
}
}, 100);
let error = null;
try {
// await some async operation
await func();
} catch (err) {
error = err
} finally {
clearInterval(intervalId);
if (this.statusMessage == '') this.statusMessage = 'Loading';
if (error) {
// if an error was caught, mark the bar as "Failed!" and post the message
if (this.currentStep <= this.totalSteps && this.currentStep >= 0 && this.totalSteps >= 1) {
process.stdout.write(`\r\x1b[31m[✓] ${Math.round(this.currentStep / this.totalSteps * 10000)/100}% (Step: ${this.currentStep}/${this.totalSteps})\x1b[0m - ${this.statusMessage}... >> FAILED!\x1b[?25h \n`);
} else {
process.stdout.write(`\r\x1b[32m[✓]\x1b[0m - ${this.statusMessage}... >> FAILED!\x1b[?25h \n`);
}
console.error(`\x1b[31m └-${error}\x1b[?25h\x1b[0m`);
this.currentStep = 0;
this.totalSteps = 0;
this.statusMessage = '';
return error;
} else {
// if there was no error, stop the bar and return to the script
if (this.currentStep <= this.totalSteps && this.currentStep >= 0 && this.totalSteps >= 1) {
process.stdout.write(`\r\x1b[32m[✓] ${Math.round(this.currentStep / this.totalSteps * 10000)/100}% (Step: ${this.currentStep}/${this.totalSteps})\x1b[0m - ${this.statusMessage}... >> Done!\x1b[?25h \n`);
} else {
process.stdout.write(`\r\x1b[32m[✓]\x1b[0m - ${this.statusMessage}... >> Done!\x1b[?25h \n`);
}
this.currentStep = 0;
this.totalSteps = 0;
this.statusMessage = '';
return;
}
}
}
}
And for the index.js:
const loading = require('./loading.js');
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function something() {
loading.totalSteps = 3;
loading.statusMessage = "doing something";
sleep(20000);
loading.currentStep = 1;
loading.statusMessage = "working harder";
sleep(2000);
loading.currentStep = 2;
loading.statusMessage = "almost done";
sleep(2000);
loading.currentStep = 3;
}
async function main() {
await loading.spinner(something);
}
main();
The output for index.js script is as follows:
[✓] 100% (Step: 3/3) - almost done... >> Done!
This is shown almost as soon as it is run, without waiting for the something() function to finish waiting, however the program does not close until the something() function finishes waiting. I don't understand why the spinner never shows up though. any ideas?
on a side note, Is there any way to make it so that the entire line is overwritten every frame? for example, if the statusMessage text is shorter than the frame before it, the end of the last frame persists into the second one because the rest was not overwritten with new text. My hacky solution to this was to just append a lot of spaces to the end of each line so that so long as the message text is short you wont see it but I realize it wont fix the problem, how does one measure the length of the last line and append the correct number of spaces to the end of the new line or clear the line entirely before writing the next one.
I understand this is probably a lot of stuff to go through so I apologize. Thank you in advance! If there's any other information I should provide please ask!

why is my script looping without finishing?

when i run this on the website https://www.nike.com/experiences it does not stop opening tabs however unless i missed something it shouldn't loop much more than twice because of these infos returns integer 4:
dispo = document.querySelectorAll('.grid')[5].querySelectorAll('a').length
var checkInterval = setInterval(() => {
if (document.documentElement.innerText.includes('Paris') == true){
dispo = document.querySelectorAll('.grid')[5].querySelectorAll('a').length
for (i=0; i<dispo+1;i += 2){
window.open(document.querySelectorAll('.grid')[5].querySelectorAll('a')[i].href,"_blank")
}
clearInterval(checkInterval )
};
}, 2000);
Maybe this document.querySelectorAll('.grid')[5].querySelectorAll('a')[i] is undefined, that undefined.href throw an exception, so it failed to execute clearInterval.

Discord.JS Command Issue

I've been working on this command (that I have a problem with) where when the user says u?hi, the bot replies before putting you in a set. You are then put in a timeout for 20 seconds, and while you are in the timeout, if you type u?hi, the bot replies gotta wait x seconds. When the timeout ends, they can type u?hi and the cycle keeps going.
However, I've encountered a problem. In my code, after doing u?hi, I get put in a timeout (just like how I planned). However, while in the timeout, if I type u?hi while lets say 1 seconds into the timeout, instead of the bot saying gotta wait 19 more seconds, the bot says gotta wait 19 more seconds and then starts counting down all the way to 0. Here's what I mean (Screenshot):
Here's my code:
const intervalSet = new Set();
bot.on("message", msg => {
let args = msg.content.substring(prefix.length).split(" ");
switch (args[0]) {
case "hi":
var interval = 20;
var intervalID;
if (intervalSet.has(msg.author.id)) {
intervalID = setInterval(() => {
interval -= 1;
if (interval !== 0 && args[0] === 'hi') {
msg.channel.send(`gotta wait ${interval} more seconds`);
}
if (interval === 0) {
clearInterval(intervalID);
msg.channel.send(`Ended`);
intervalSet.delete(msg.author.id);
}
}, 1000);
} else {
intervalSet.add(msg.author.id);
msg.channel.send("heyy");
}
}
});
I've tried moving the
if (interval !== 0 && args[0] === 'hi') {
msg.channel.send(`gotta wait ${interval} more seconds`);
}
part to other places of the code and changing it up but nothing seems to work. What can I do about this?
Yes that's normal with your code. What you need to do is to create a map, named cooldown. User IDs will be linked with times, so you'll be able to calculate for how long the user is in cooldown.
Here is an update of your code:
const cooldown = new Map();
bot.on("message", msg => {
let args = msg.content.substring(prefix.length).split(" ");
switch (args[0]) {
case "hi":
var interval = 20000; // use milliseconds instead of seconds
var intervalID;
if (cooldown.has(msg.author.id)) {
let cooldownTime = Date.now() - cooldown.get(msg.author.id);
let timeToWait = (interval-cooldownTime)/1000;
if(cooldownTime < interval) {
return message.channel.send(`gotta wait ${timeToWait} more seconds`);
}
}
cooldown.set(msg.author.id, Date.now());
msg.channel.send("heyy");
}
});
If you have any questions, don't hesitate to post a comment!

Several setTimeout() functions [duplicate]

I'm trying to send emails with a 10 seconds delay between. I wrote this code:
$(document).ready(function() {
for (i = 0; i < 20; i++) {
setTimeout("SendEmail(" + i + ")", 5000);
}
});
function SendEmail(id) {
$.get("mimesender.php?id=" + id, function(data) {
var toAppend = "<span> " + data + "</span>"
$("#sentTo").append(toAppend);
});
}
server side code(php) gets the id and selects the email with the specified id from the database
$query="select email from clienti where id =".$id;
then sends the email, and sends back the current email
echo email;
However, something is wrong here. It seems like the the js function waits for 5 seconds, and then displays all the 20 email addresses at once.
Can you tell me what I'm doing wrong ? any "sleep "workaround will be greatly appreciated :)
Use interval instead of loop.
Working demo: http://jsfiddle.net/xfVa9/2/
$(document).ready(function() {
var tmr;
var i=0;
tmr=setInterval(function(){
if(i<20){
SendEmail(i);
alert("Sent "+i)
i++;
}else{
clearInterval(tmr);
}
},5000)
});
You should create a function which calls itself after 5 seconds
var i=0;
function sendEmailNow() {
SendEmail(i);
++i;
if(i<20) {
setTimeout(sendEmailNow, 5000);
}
}
What happens is that you call setTimeout 20 times, just after one another, with a timeout of 5 seconds. So naturally, all emails gets sent at once. You could change the loop to look like this:
for (i=0;i<20;i++) {
setTimeout("SendEmail("+ i + ")",(i+1)*5000);
}
There's alot of other options though, and they'd depend on just what suits your specific problem best.
First, pass a function to setTimeout.
Secondly, you'd be better off if you set the timeout for the next one in the queue after the current one is finished.
In the for loop:
sendEmail(0); // start sending first
and in the callback:
, function(data) {
if(id < 19) { // if next should be sent
setTimeout(function() {
SendEmail(id + 1);
}, 5000);
}
var toAppend = "<span> " + data + "</span>"
$("#sentTo").append(toAppend);
}
Your loop is setting up 20 timers to wait 5 seconds, then letting them all go at once.
Try something like this:
var email_count = 20;
var sendMails = function(){
SendEmail(email_count--);
if(email_count > 0){
setTimeout(sendMails, 5000);
}
}
setTimeout(sendMails, 5000)
Avoid jQuery. Learn JavaScript.
var i = 0; (otherwise leak into outer scope or runtime error)
Extra closure:
window.setTimeout(
(function (j) {
return function () {
sendEmail(j);
};
}(i)),
i * 10000);
sendEmail (code style: not a constructor)
You really want to escape $id in the server-side code to prevent SQL injection.

Reset timeout on event with RxJS

I'm experimenting with RxJS (with the JQuery extension) and I'm trying to solve the following use case:
Given that I have two buttons (A & B) I'd like to print a message if a certain "secret combination" is clicked within a given timeframe. For example the "secret combination" could be to click "ABBABA" within 5 seconds. If the combination is not entered within 5 seconds a timeout message should be displayed. This is what I currently have:
var secretCombination = "ABBABA";
var buttonA = $("#button-a").clickAsObservable().map(function () { return "A"; });
var buttonB = $("#button-b").clickAsObservable().map(function () { return "B"; });
var bothButtons = Rx.Observable.merge(buttonA, buttonB);
var outputDiv = $("#output");
bothButtons.do(function (buttonName) {
outputDiv.append(buttonName);
}).bufferWithTimeOrCount(5000, 6).map(function (combination) {
return combination.reduce(function (combination, buttonName) {
return combination + buttonName;
}, "");
}).map(function (combination) {
return combination === secretCombination;
}).subscribe(function (successfulCombination) {
if (successfulCombination) {
outputDiv.html("Combination unlocked!");
} else {
outputDiv.html("You're not fast enough, try again!");
}
});
While this works fairly well it's not exactly what I want. I need the bufferWithTimeOrCount to be reset when button A is pressed for the first time in a new timeframe. What I'm looking for is that as soon as the secret combination is pressed (ABBABA) I'd like "Combination unlocked!" to be shown (I don't want to wait for the time window to be expired).
Throttle is the typical operator for the delaying with reactive resetting effect you want.
Here's how you can use throttle in combination with scan to gather the combination inputted before the 5 seconds of silence:
var evaluationStream = bothButtons
.merge(bothButtons.throttle(5000).map(function(){return "reset";})) // (2) and (3)
.scan(function(acc, x) { // (1)
if (x === "reset") return "";
var newAcc = acc + x;
if (newAcc.length > secretCombination.length) {
return newAcc.substr(newAcc.length - secretCombination.length);
}
else {
return newAcc;
}
})
.map(function(combination) {
return combination === secretCombination;
});
var wrongStream = evaluationStream
.throttle(5000)
.filter(function(result) { return result === false; });
var correctStream = evaluationStream
.filter(function(result) { return result === true; });
wrongStream.subscribe(function() {
outputDiv.html("Too slow or wrong!");
});
correctStream.subscribe(function() {
outputDiv.html("Combination unlocked!");
});
(1) We scan to concatenate the input characters. (2) Throttle waits for 5 seconds of event silence and emits the last event before that silence. In other words, it's similar to delay, except it resets the inner timer when a new event is seen on the source Observable. We need to reset the scan's concatenation (1), so we just map the same throttled Observable to "reset" flags (3), which the scan will interpret as clearing the accumulator (acc).
And here's a JSFiddle.

Categories

Resources