SOLVED: Updating async functions between modules - javascript

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!

Related

Javascript: Infinite loop in webworker [duplicate]

I want to sort an array, using Web Workers. But this array might receive new values over time, while the worker is still performing the sort function.
So my question is, how can I "stop" the sorting computation on the worker after receiving the new item, so it can perform the sort on the array with that item, while still keeping the sorting that was already made?
Example:
let worker = new Worker('worker.js');
let list = [10,1,5,2,14,3];
worker.postMessage({ list });
setInterval(() => worker.postMessage({ num: SOME_RANDOM_NUM, list }), 100);
worker.onmessage = event => {
list = event.data.list;
}
So lets say that, I've passed 50, the worker made some progress in the sorting before that and now I have something like this:
[1, 2, 3, 10, 5, 14, 50]. Which means the sorting stopped at index 3. So I pass this new array back to the worker, so it can continue the sorting from position 3.
How can I accomplish that, since there is no way to pause/resume a web worker?
Even though the Worker works on an other thread than the one of your main page, and can thus run continuously without blocking the UI, it still runs on a single thread.
This means that until your sort algorithm has finished, the Worker will delay the execution of the message event handler; it is as blocked as would be the main thread.
Even if you made use of an other Worker from inside this worker, the problem would be the same.
The only solution would be to use a kind of generator function as the sorter, and to yield it every now and then so that the events can get executed.
But doing this will drastically slow down your sorting algorithm.
To make it better, you could try to hook to each Event Loop, thanks to a MessageChannel object: you talk in one port and receive the message in the next Event loop. If you talk again to the other port, then you have your own hook to each Event loop.
Now, the best would be to run a good batch in every of these Event loop, but for demo, I'll call only one instance of our generator function (that I borrowed from this Q/A)
const worker = new Worker(getWorkerURL());
worker.onmessage = draw;
onclick = e => worker.postMessage(0x0000FF/0xFFFFFF); // add a red pixel
// every frame we request the current state from Worker
function requestFrame() {
worker.postMessage('gimme a frame');
requestAnimationFrame(requestFrame);
}
requestFrame();
// drawing part
const ctx = canvas.getContext('2d');
const img = ctx.createImageData(50, 50);
const data = new Uint32Array(img.data.buffer);
ctx.imageSmoothingEnabled = false;
function draw(evt) {
// converts 0&1 to black and white pixels
const list = evt.data;
list.forEach((bool, i) =>
data[i] = (bool * 0xFFFFFF) + 0xFF000000
);
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.putImageData(img,0,0);
// draw bigger
ctx.scale(5,5);
ctx.drawImage(canvas, 0,0);
}
function getWorkerURL() {
const script = document.querySelector('[type="worker-script"]');
const blob = new Blob([script.textContent]);
return URL.createObjectURL(blob);
}
body{
background: ivory;
}
<script type="worker-script">
// our list
const list = Array.from({length: 2500}).map(_=>+(Math.random()>.5));
// our sorter generator
let sorter = bubbleSort(list);
let done = false;
/* inner messaging channel */
const msg_channel = new MessageChannel();
// Hook to every Event loop
msg_channel.port2.onmessage = e => {
// procede next step in sorting algo
// could be a few thousands in a loop
const state = sorter.next();
// while running
if(!state.done) {
msg_channel.port1.postMessage('');
done = false;
}
else {
done = true;
}
}
msg_channel.port1.postMessage("");
/* outer messaging channel (from main) */
self.onmessage = e => {
if(e.data === "gimme a frame") {
self.postMessage(list);
}
else {
list.push(e.data);
if(done) { // restart the sorter
sorter = bubbleSort(list);
msg_channel.port1.postMessage('');
}
}
};
function* bubbleSort(a) { // * is magic
var swapped;
do {
swapped = false;
for (var i = 0; i < a.length - 1; i++) {
if (a[i] > a[i + 1]) {
var temp = a[i];
a[i] = a[i + 1];
a[i + 1] = temp;
swapped = true;
yield swapped; // pause here
}
}
} while (swapped);
}
</script>
<pre> click to add red pixels</pre>
<canvas id="canvas" width="250" height="250"></canvas>
Note that the same can be achieved with an async function, which may be more practical in some cases:
const worker = new Worker(getWorkerURL());
worker.onmessage = draw;
onclick = e => worker.postMessage(0x0000FF/0xFFFFFF); // add a red pixel
// every frame we request the current state from Worker
function requestFrame() {
worker.postMessage('gimme a frame');
requestAnimationFrame(requestFrame);
}
requestFrame();
// drawing part
const ctx = canvas.getContext('2d');
const img = ctx.createImageData(50, 50);
const data = new Uint32Array(img.data.buffer);
ctx.imageSmoothingEnabled = false;
function draw(evt) {
// converts 0&1 to black and white pixels
const list = evt.data;
list.forEach((bool, i) =>
data[i] = (bool * 0xFFFFFF) + 0xFF000000
);
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.putImageData(img,0,0);
// draw bigger
ctx.scale(5,5);
ctx.drawImage(canvas, 0,0);
}
function getWorkerURL() {
const script = document.querySelector('[type="worker-script"]');
const blob = new Blob([script.textContent]);
return URL.createObjectURL(blob);
}
body{
background: ivory;
}
<script type="worker-script">
// our list
const list = Array.from({length: 2500}).map(_=>+(Math.random()>.5));
// our sorter generator
let done = false;
/* outer messaging channel (from main) */
self.onmessage = e => {
if(e.data === "gimme a frame") {
self.postMessage(list);
}
else {
list.push(e.data);
if(done) { // restart the sorter
bubbleSort(list);
}
}
};
async function bubbleSort(a) { // async is magic
var swapped;
do {
swapped = false;
for (var i = 0; i < a.length - 1; i++) {
if (a[i] > a[i + 1]) {
const temp = a[i];
a[i] = a[i + 1];
a[i + 1] = temp;
swapped = true;
}
if( i % 50 === 0 ) { // by batches of 50?
await waitNextTask(); // pause here
}
}
} while (swapped);
done = true;
}
function waitNextTask() {
return new Promise( (resolve) => {
const channel = waitNextTask.channel ||= new MessageChannel();
channel.port1.addEventListener("message", (evt) => resolve(), { once: true });
channel.port2.postMessage("");
channel.port1.start();
});
}
bubbleSort(list);
</script>
<pre> click to add red pixels</pre>
<canvas id="canvas" width="250" height="250"></canvas>
There are two decent options.
Option 1: Worker.terminate()
The first is just to kill your existing web worker and start a new one. For that you can use Worker.terminate().
The terminate() method of the Worker interface immediately terminates the Worker. This does not offer the worker an opportunity to finish its operations; it is simply stopped at once.
The only downsides of this approach are:
You lose all worker state. If you had to copy a load of data into it for the request you have to do it all again.
It involves thread creation and destruction, which isn't as slow as most people think but if you terminate web workers a lot it might cause issues.
If neither of those are an issue it is probably the easiest option.
In my case I have lots of state. My worker is rendering part of an image, and when the user pans to a different area I want it to stop what it is doing and start rendering the new area. But the data needed to render the image is pretty huge.
In your case you have the state of your (presumably huge) list that you don't want to use.
Option 2: Yielding
The second option is basically to do cooperative multitasking. You run your computation as normal, but every now and then you pause (yield) and say "should I stop?", like this (this is for some nonsense calculation, not sorting).
let requestId = 0;
onmessage = event => {
++requestId;
sortAndSendData(requestId, event.data);
}
function sortAndSendData(thisRequestId, data) {
let isSorted = false;
let total = 0;
while (data !== 0) {
// Do a little bit of computation.
total += data;
--data;
// Check if we are still the current request ID.
if (thisRequestId !== requestId) {
// Data was changed. Cancel this sort.
return;
}
}
postMessage(total);
}
This won't work though because sortAndSendData() runs to completion and blocks the web worker's event loop. We need some way to yield just before thisRequestId !== requestId. Unfortunately Javascript doesn't quite have a yield method. It does have async/await so we might try this:
let requestId = 0;
onmessage = event => {
console.log("Got event", event);
++requestId;
sortAndSendData(requestId, event.data);
}
async function sortAndSendData(thisRequestId, data) {
let isSorted = false;
let total = 0;
while (data !== 0) {
// Do a little bit of computation.
total += data;
--data;
await Promise.resolve();
// Check if we are still the current request ID.
if (thisRequestId !== requestId) {
console.log("Cancelled!");
// Data was changed. Cancel this sort.
return;
}
}
postMessage(total);
}
Unfortunately it doesn't work. I think it's because async/await executes things eagerly using "microtasks", which get executed before pending "macrotasks" (our web worker message) if possible.
We need to force our await to become a macrotask, which you can do using setTimeout(0):
let requestId = 0;
onmessage = event => {
console.log("Got event", event);
++requestId;
sortAndSendData(requestId, event.data);
}
function yieldToMacrotasks() {
return new Promise((resolve) => setTimeout(resolve));
}
async function sortAndSendData(thisRequestId, data) {
let isSorted = false;
let total = 0;
while (data !== 0) {
// Do a little bit of computation.
total += data;
--data;
await yieldToMacrotasks();
// Check if we are still the current request ID.
if (thisRequestId !== requestId) {
console.log("Cancelled!");
// Data was changed. Cancel this sort.
return;
}
}
postMessage(total);
}
This works! However it is extremely slow. await yieldToMacrotasks() takes approximately 4 ms on my machine with Chrome! This is because browsers set a minimum timeout on setTimeout(0) of something like 1 or 4 ms (the actual minimum seems to be complicated).
Fortunately another user pointed me to a quicker way. Basically sending a message on another MessageChannel also yields to the event loop, but isn't subject to the minimum delay like setTimeout(0) is. This code works and each loop only takes ~0.04 ms which should be fine.
let currentTask = {
cancelled: false,
}
onmessage = event => {
currentTask.cancelled = true;
currentTask = {
cancelled: false,
};
performComputation(currentTask, event.data);
}
async function performComputation(task, data) {
let total = 0;
let promiseResolver;
const channel = new MessageChannel();
channel.port2.onmessage = event => {
promiseResolver();
};
while (data !== 0) {
// Do a little bit of computation.
total += data;
--data;
// Yield to the event loop.
const promise = new Promise(resolve => {
promiseResolver = resolve;
});
channel.port1.postMessage(null);
await promise;
// Check if this task has been superceded by another one.
if (task.cancelled) {
return;
}
}
// Return the result.
postMessage(total);
}
I'm not totally happy about it - it relies on postMessage() events being processed in FIFO order, which I doubt is guaranteed. I suspect you could rewrite the code to make it work even if that isn't true.
You can do it with some trick – with the help of setTimeout function interrupting. For example it is not possible without an addition thread to execute 2 functions parallel, but with setTimeout function interrupting trick we can do it like follows:
Example of parallel execution of functions
var count_0 = 0,
count_1 = 0;
function func_0()
{
if(count_0 < 3)
setTimeout(func_0, 0);//the same: setTimeout(func_0);
console.log('count_0 = '+count_0);
count_0++
}
function func_1()
{
if(count_1 < 3)
setTimeout(func_1, 0);
console.log('count_1 = '+count_1)
count_1++
}
func_0();
func_1();
You will get this output:
count_0 = 0
count_1 = 0
count_0 = 1
count_1 = 1
count_0 = 2
count_1 = 2
count_0 = 3
count_1 = 3
Why is it possible? Because the setTimeout function needs some time to be executed. And this time is even enought for the execution of some part from your following code.
Solution for you
For this case you have to write your own array sort function (or you can also use the following function from me) because we can not interrupt the native sort function. And in this your own function you have to use this setTimeout function interrupting trick. And you can receive your message event notification.
In the following example I have the interrupting in the half length of my array, and you can change it if you want.
Example with custom sort function interrupting
var numbers = [4, 2, 1, 3, 5];
// this is my bubble sort function with interruption
/**
* Sorting an array. You will get the same, but sorted array.
* #param {array[]} arr – array to sort
* #param {number} dir – if dir = -1 you will get an array like [5,4,3,2,1]
* and if dir = 1 in opposite direction like [1,2,3,4,5]
* #param {number} passCount – it is used only for setTimeout interrupting trick.
*/
function sortNumbersWithInterruption(arr, dir, passCount)
{
var passes = passCount || arr.length,
halfOfArrayLength = (arr.length / 2) | 0; // for ex. 2.5 | 0 = 2
// Why we need while loop: some values are on
// the end of array and we have to change their
// positions until they move to the first place of array.
while(passes--)
{
if(!passCount && passes == halfOfArrayLength)
{
// if you want you can also not write the following line for full break of sorting
setTimeout(function(){sortNumbersWithInterruption(arr, dir, passes)}, 0);
/*
You can do here all what you want. Place 1
*/
break
}
for(var i = 0; i < arr.length - 1; i++)
{
var a = arr[i],
b = arr[i+1];
if((a - b) * dir > 0)
{
arr[i] = b;
arr[i+1] = a;
}
}
console.log('array is: ' + arr.join());
}
if(passCount)
console.log('END sring is: ' + arr.join());
}
sortNumbersWithInterruption(numbers, -1); //without passCount parameter
/*
You can do here all what you want. Place 2
*/
console.log('The execution is here now!');
You will get this output:
array is: 4,2,3,5,1
array is: 4,3,5,2,1
The execution is here now!
array is: 4,5,3,2,1
array is: 5,4,3,2,1
END sring is: 5,4,3,2,1
You can do it with insertion sort (kind of).
Here is the idea:
Start your worker with an internal empty array (empty array is sorted obviously)
Your worker receives only elements not the entire array
Your worker insert any received element right in correct position into the array
Every n seconds, the worker raises a message with the current array if it has changed after the last event. (If you prefer, you can send the array on every insertion, but is more efficient to buffer somehow)
Eventually, you get the entire array, if any item is added, you will receive the updated array to.
NOTE: Because your array is always sorted, you can insert in correct position using binary search. This is very efficient.
I think the case comes down to careful management of postMessage calls and amount of data passed to be processed at a time. Was dealing with problem of this kind - think about not sending all new data into the function at once but rather creating your own queue and when small enough portion of the task has been acomplished by webworker thread send a message back to the main thread and decide to send the next portion, wait or quit.
In Your case, e.g. one time You get 9000 new items, next 100k - maybe create a queue/buffer that adds next 10k new elements each time webworker is done processing last data change.
const someWorker = new Worker('abc.js');
var processingLock = false;
var queue = [];
function newDataAction(arr = null) {
if (arr != null) {
queue = queue.concat(arr);
}
if (!processingLock) {
processingLock = true;
var data = [];
for (let i = 0; i < 10000 && queue.length > 0; i++) {
data.push(queue.pop());
}
worker.postMessage(data);
}
}
someWorker.addEventListener('message', function(e) {
if (e.data == 'finished-last-task') {
processingLock = false;
if (queue.length > 0) {
newDataAction();
}
}
});
Worked through many sorting algorithms and I don't see how sending new data into an sorting algorithm with partially sorted array makes much difference in terms of compuation time from sorting them both sequentially and performing a merge.

How to execute code in order in a while loop in Javascript?

I'm trying to run some JavaScript code on Postman, but I can't find a way for it to run in the order that I need. This is what I'm trying to do:
Retrieve the API response and verify if the "pending" array contains any item
If id does, I'll save the id of the record (orderId) in an environment variable to use in my actual request
At this point I would set found = true and break the loop when it leaves the setTimout function
Note: I created the function to introduce 400ms delay between the attempts, as it will allow the pending array to be populated
var found = false;
var counter = 0;
while (counter < 10) {
setTimeout(async () => {
var size = await response.json().pending.length;
if (size > 0) {
var orderId = response.json().pending[0].orderId;
pm.environment.set("current_order", orderId);
found = true;
}
}, [400]);
console.log(found);
if (found) { break; }
counter++;
}
My problem is that the part that is outside the setTimeout function executes first, so it will never satisfy the condition "If (found)". It always executes the code 10 times, even if the record is found in the first attempt.
My question is: How can I write it in order to check if the record was found after each attempt and break from the loop if positive?
Thanks!
As suggested above, you might be able to solve this issue with a simpler recursive function. An example on how it would look:
var found = false;
var size = 0;
async function checkResponse() {
var size = await response.json().pending.length;
if (size > 0) {
var orderId = await response.json().pending[0].orderId;
pm.environment.set('current_order', orderId);
found = true;
}
if (!found) {
await checkResponse();
}
}
await checkResponse();
Here is a synchronous test for the logic above:
var found = false;
// var size = 0;
var size = -5;
function checkResponse() {
// var size = await response.json().pending.length;
size += 1;
console.log(size);
if (size > 0) {
// var orderId = response.json().pending[0].orderId;
// pm.environment.set('current_order', orderId);
console.log("pm.environment.set('current_order', orderId);");
found = true;
}
console.log('END?');
console.log(found);
if (!found) {
checkResponse();
console.log('checkResponse();');
}
}
checkResponse();
Output:
-4
END?
false
-3
END?
false
-2
END?
false
-1
END?
false
0
END?
false
1
pm.environment.set('current_order', orderId);
END?
true
checkResponse();
checkResponse();
checkResponse();
checkResponse();
checkResponse();

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

A pattern to queue notifications?

I created a library to pop up some toast notifications and I tried to put a limit on the maximum notifications on screen.
I managed to extract the idea into a plunker (don't mind the code, it is only to solve the issue).
I have a function to create those toasts:
function createToast() {
var body = $document.find('body').eq(0);
var toast = {};
toast.id = index++;
toast.el = angular.element('<div class="toast">Toast ' + toast.id + '</div>');
toast.el = $compile(toast.el)($scope);
if (maxOpened && toasts.length >= maxOpened) {
remove(toasts[0].id);
}
toasts.push(toast);
$animate.enter(toast.el, body).then(function() {
$timeout(function() {
remove(toast.id);
}, 3000);
});
}
Basically it creates a new object with an el and then animates it out on the body. Notice that if the maxOpened is reached it removes the first one.
function remove(id) {
var toast = findToast(id);
if (toast) {
$animate.leave(toast.el).then(function() {
var index = toasts.indexOf(toast);
toasts.splice(index, 1);
});
}
function findToast(toastId) {
for (var i = 0; i < toasts.length; i++) {
if (toasts[i].id === id) {
return toasts[i];
}
}
}
}
Find the toast, animate the leave and then delete it.
If I do a $interval on them, let's say 600ms it works.
Try here: http://plnkr.co/edit/lDnT57FPadCt5Ir5wHuK?p=preview
If you lower it to something like 100ms it starts to break, not only ignoring the max but also leaving some orphan toasts that won't get deleted.
So I am not sure what could be a good solution here. My best guess is to provide a queue so I start to drain it as soon as a toast get removed but so far, I didn't make it.
The probably simplest solution would be to add a deferred to each toast and only start to animate the toast when the limit is not or no longer reached.
You start by adding a deferred and resolve it immediately, if the limit is not reached yet or the limit can be ignored:
toast.slotOpen = $q.defer();
toasts.push(toast);
if (maxOpened && toasts.length <= maxOpened || !maxOpened) { // i guess 0 or a falsy value means to ignore the limit
toast.slotOpen.resolve();
}
You only start the animation, when a slot is open:
toast.slotOpen.promise.then(function() {
$animate.enter(toast.el, body).then(function() {
The last thing to do is to resolve the deferred when a new slot gets opened after an old toast has been removed:
$animate.leave(toast.el).then(function() {
var index = toasts.indexOf(toast);
toasts.splice(index, 1);
if (maxOpened && toasts.length >= maxOpened) {
toasts[maxOpened - 1].slotOpen.resolve();
}
I have adjusted your code and created a new Plunker.

Categories

Resources