why is my script looping without finishing? - javascript

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.

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!

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!

Make a loop stop on variable being false

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);

while loop not behaving as expected in JavaScript

First of all, it must be said that this may be a setState issue as I have been having some trouble with that but I cannot work out why this while loop wont work.
code:
dealerTwisted = () => {
console.log(this.state.dealersOverallTotal, 'total on entry');
let looping = true;
while(looping){
console.log(this.state.dealersOverallTotal,'same');
if(this.state.dealersOverallTotal < 17){
this.deal2Dealer();
let dealersDeck = this.state.dealersDeck;
let newDealersDeckTotal = [];
for (var i=0; i < dealersDeck.length; i++){
newDealersDeckTotal.push(dealersDeck[i].rankValue)
}
let total = newDealersDeckTotal.reduce(function(a, b) {
return a + b;
},
0);
console.log(total, 'tot');
this.setState({ dealersOverallTotal: total });
}
else{
console.log(this.state.dealersOverallTotal, 'logging as greater than 17');
if(this.state.playersOverallTotal > this.state.dealersOverallTotal){
console.log('player wins!');
looping = false;
}
else if (this.state.playersOverallTotal > this.state.dealersOverallTotal){
console.log('its a tie!');
looping = false;
}
else {
console.log('dealer wins!');
looping = false;
}
}
}
};
The way I think it is and the way I want it to work is as follows:
the function gets called, the loop is entered, you enter the if block if this.state.dealersOverallTotal is less than 17. that total then gets updated and then you begin the loop again. you continue to enter the if block until you get to a number higher than 17. once you do you enter the else block and then whichever condition you hit in there, you break out.
interesting I have just debugged and found that this line (the fist console log) console.log(this.state.dealersOverallTotal, 'total on entry'); gives me the same number every time, i.e. not updating once inside. can anyone see why?
React's setState is asynchronous, so your this.state.dealersOverallTotal never reaches 17.
setState in reactjs is Async or Sync

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