It's sends 4 times the amount of members - node.js

So I want my bot in a certain server to send a dm to a random member every 10 minutes. And when my bot has sent everyone from the server a dm it sends a complete message.
But when i start the bot it sends 4 times the amount of members
if (message.content.startsWith(botconfig.prefix + 'dmall')) {
console.log("demo");
var list = message.guild.members.array();
sendMessage(list);
}
});
function sendMessage(list) {
setTimeout(function () {
for (i = 0; i < list.length; i++) {
console.log(list.length);
}
console.log("I'm done, mate!");
sendMessage(list);
}, 10 * 1000);
}
CONSOLE:
demo
4 (is the amount of the members)
4
4
4
I'm done mate!

This part of your code:
for (i = 0; i < list.length; i++) {
console.log(list.length);
}
tells Javascript to run the statement:
console.log(list.length);
list.length times. If list.length is 4 which it appears to be here, then you will see in the console
4
4
4
4
That's what the code was instructed to do.
I don't see any reason why you'd put that in a loop unless you want to output each array element separately. So, if you want to just output the length once, then replace this:
for (i = 0; i < list.length; i++) {
console.log(list.length);
}
with this:
console.log(list.length);
In addition, if you were to use a for loop, you MUST declare all variables that you use. So, this:
for (i = 0; i < list.length; i++) {
is very dangerous. It relies on a higher scoped i which can easily conflict with other higher scoped i variables and create hard-to-figure out bugs. Every for loop should declare it's own loop variable as in:
for (let i = 0; i < list.length; i++) {
If you run your code in strict mode (which you should), the above for loop declaration would likely cause an error (which is a good thing because you'd immediately see the coding error and fix it).

There are a couple of things wrong here.
For of: JavaScript variable scoping messing you up (Different example here
Second of all: setTimeout is not the same as setInterval.
Completely fixing your issue would be:
// Use the discord API
let users = ['justme']
function getUsers() {
// Mutate just to display a new one each time
users = [...users, users.length.toString()];
return users
}
function notifyUsers() {
// Retrieve all users now
const currentUsers = getUsers()
// Send a message to each user
currentUsers.forEach(user => sendMessage(user))
console.log('I have sent everyone a message')
}
function sendMessage(user) {
console.log(`Sending message to ${user}`)
}
// Start the application by looping every <whatever>
setInterval(() => notifyUsers(), 1000)

Related

Alternating messages on chatbot creation

I'm doing a chatbot with Node JS for Instagram using this library available on github(https://github.com/Androz2091/insta.js.git). I'm currently testing the feature through the online replit platform, I'm getting the message from the instagram user and sending it through a url to the reply service
I have a difficulty that would be the bot's answer. It must follow a message structure, however, this structure is often toggled.
I send in the chat:
-Hi
The bot responds in the console:
-Hello!
-How can I help you?
but in the chat he responds like this:
-How can I help you?
-Hello!
Below is the code I used to go through the arrays I receive and send the messages
I tried using a switch case to try to solve the problem, but it still remains the same.
node-fetch(`instagram?mensage=${message}&user=${message.author.fullName}&session=${client.user}`)
.then(res => res.json())
.then(json => {
for (var i = 0; i < json.length; i++) {
message.chat.sendMessage(json[i].text || json[i].title);
console.log(json[i].text || json[i].title)
for (var key in json[i]) {
/* switch case
switch(json[i][key]){
case json[i].text:
message.chat.sendMessage(json[i].text);
break;
case json[i].title:
message.chat.sendMessage(json[i].title);
break;
}*/
if (json[i][key].length == 3) {
for (var j = 0; j < json[i][key].length; j++) {
message.chat.sendMessage(json[i][key][j].label);
console.log(json[i][key][j].label);
}
}
Does anyone know why this happens?
I already thank you.
sendMessage returns Promise, but you are using it as a normal function.
You can read about javascript promises here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises.
In your code, it should look something like this
...
.then(async json => {
for (var i = 0; i < json.length; i++) {
await message.chat.sendMessage(json[i].text || json[i].title);
...
}
});

Nodejs - Fire multiple API calls while limiting the rate and wait until they are all done

My issues
Launch 1000+ online API that limits the number of API calls to 10 calls/sec.
Wait for all the API calls to give back a result (or retry), it can take 5 sec before the API sends it data
Use the combined data in the rest of my app
What I have tried while looking at a lot of different questions and answers here on the site
Use promise to wait for one API request
const https = require("https");
function myRequest(param) {
const options = {
host: "api.xxx.io",
port: 443,
path: "/custom/path/"+param,
method: "GET"
}
return new Promise(function(resolve, reject) {
https.request(options, function(result) {
let str = "";
result.on('data', function(chunk) {str += chunk;});
result.on('end', function() {resolve(JSON.parse(str));});
result.on('error', function(err) {console.log("Error: ", err);});
}).end();
});
};
Use Promise.all to do all the requests and wait for them to finish
const params = [{item: "param0"}, ... , {item: "param1000+"}]; // imagine 1000+ items
const promises = [];
base.map(function(params){
promises.push(myRequest(params.item));
});
result = Promise.all(promises).then(function(data) {
// doing some funky stuff with dat
});
So far so good, sort of
It works when I limit the number of API requests to a maximum of 10 because then the rate limiter kicks in. When I console.log(promises), it gives back an array of 'request'.
I have tried to add setTimeout in different places, like:
...
base.map(function(params){
promises.push(setTimeout(function() {
myRequest(params.item);
}, 100));
});
...
But that does not seem to work. When I console.log(promises), it gives back an array of 'function'
My questions
Now I am stuck ... any ideas?
How do I build in retries when the API gives an error
Thank you for reading up to hear, you are already a hero in my book!
When you have a complicated control-flow using async/await helps a lot to clarify the logic of the flow.
Let's start with the following simple algorithm to limit everything to 10 requests per second:
make 10 requests
wait 1 second
repeat until no more requests
For this the following simple implementation will work:
async function rateLimitedRequests (params) {
let results = [];
while (params.length > 0) {
let batch = [];
for (i=0; i<10; i++) {
let thisParam = params.pop();
if (thisParam) { // use shift instead
batch.push(myRequest(thisParam.item)); // of pop if you want
} // to process in the
// original order.
}
results = results.concat(await Promise.all(batch));
await delayOneSecond();
}
return results;
}
Now we just need to implement the one second delay. We can simply promisify setTimeout for this:
function delayOneSecond() {
return new Promise(ok => setTimeout(ok, 1000));
}
This will definitely give you a rate limiter of just 10 requests each second. In fact it performs somewhat slower than that because each batch will execute in request time + one second. This is perfectly fine and already meet your original intent but we can improve this to squeeze a few more requests to get as close as possible to exactly 10 requests per second.
We can try the following algorithm:
remember the start time
make 10 requests
compare end time with start time
delay one second minus request time
repeat until no more requests
Again, we can use almost exactly the same logic as the simple code above but just tweak it to do time calculations:
const ONE_SECOND = 1000;
async function rateLimitedRequests (params) {
let results = [];
while (params.length > 0) {
let batch = [];
let startTime = Date.now();
for (i=0; i<10; i++) {
let thisParam = params.pop();
if (thisParam) {
batch.push(myRequest(thisParam.item));
}
}
results = results.concat(await Promise.all(batch));
let endTime = Date.now();
let requestTime = endTime - startTime;
let delayTime = ONE_SECOND - requestTime;
if (delayTime > 0) {
await delay(delayTime);
}
}
return results;
}
Now instead of hardcoding the one second delay function we can write one that accept a delay period:
function delay(milliseconds) {
return new Promise(ok => setTimeout(ok, milliseconds));
}
We have here a simple, easy to understand function that will rate limit as close as possible to 10 requests per second. It is rather bursty in that it makes 10 parallel requests at the beginning of each one second period but it works. We can of course keep implementing more complicated algorithms to smooth out the request pattern etc. but I leave that to your creativity and as homework for the reader.

Node.js thermal printer issue in loop printing

I am developing a web application in Laravel, which sends a list of articles via JSON to a process running on Node.js (i use websocket library), which must for each of these items call a print function (i use escpos library).
When i print one or two items there are no problem, and all its OK. But when i send 3 or more items, the printer print the first ok, but with the rest it have problems. I think when i send 3 or more items, the printer is too slow and can't end the jobs.
This is the part of my code in Node.js that print the items:
for (var i = 0; i < msg.items.length; i++) {
for (var j = 0; j < msg.items[i].quantity; j++) {
print(msg.items[i]);
}
}
(Note i use 2 loop because a item can have a quantity > 1 and i have to print 1 ticket for each item and quantity)
And this is the code of the printing library (irrelevant but i leave it to clarify)
function print(item){
escpos.Image.load(__dirname + '/logo3.png', function(image){
printer
.raster(image)
.control('LF')
.style('b')
.size(2, 2)
.text(item.code)
.control('LF')
.control('LF')
.barcode(item.ean, "EAN8")
.cut();
});
I hope you can help me, thank you in advance.
I found a solution to the problem. I decided to use asynchronous requests with a timeout to wait for the printer to complete each task, along with promises. The code fragment:
function asyncFunction (item, cb) {
setTimeout(() => {
print_product(item);
console.log("Print " + item.code);
cb();
}, 1200);
}
let requests = products.reduce((promiseChain, item) => {
return promiseChain.then(() => new Promise((resolve) => {
asyncFunction(item, resolve);
}));
}, Promise.resolve());

nodejs do something after multiple functions complete

I'm having a problem that I can't seem to get around.
Imaging the following
if (users) {
for (var i = 0; i < users.length; i++) {
apiData.users = getUserDetails(users[i]);
}
}
and
if(profiles) {
for (var i = 0; i < profiles.length; i++) {
apiData.profiles = getProfileDetails(users[i]);
}
}
now, only once both of those blocks of code have been completed (i.e. the API calls have returned the results that are required), would I like to do something like this
for (var i = 0; i < users.length; i++) {
saveUserProfile(apiData)
}
Normally I handle a situation like this by wrapping the first block of code in a function that returns a callback and then running the third function afterwards, but how can I do this when there are effectively 2 separate operations happening?
To be clear, I would like to avoid saving users and profiles separately, there are various reasons for this.
I would like to know if there is any standard way of handling a situation like this as there may or may not be users or profiles, so using callbacks seems like it will not work?
You can use async module to achieve this.
Use async.parallel() function since your first and second snippets do not depend on each other, and can run asynchronously, but you wish to make third snippet run when first and second are done executing.
Assuming users, profiles, and apiData are already declared, your code would look like
function func1(callback){
if (users) {
for (var i = 0; i < users.length; i++) {
apiData.users = getUserDetails(users[i]);
}
}
//invoke callback
}
function func2(callback){
if(profiles) {
for (var i = 0; i < profiles.length; i++) {
apiData.profiles = getProfileDetails(users[i]);
}
}
//invoke callback
}
var asyncfunc = [];
asyncfunc.push(func1);
asyncfunc.push(func2);
async.parallel(asyncfunc,function(err,result){
//result is an array which receives the second argument of each function's callback.
for (var i = 0; i < users.length; i++) {
saveUserProfile(apiData)
}
})
EDIT
PS: you can also use async.series() here. Both are equivalent in this case, since the functions handled by async.parallel() here do not use timers neither perform I/O tasks.
The callback in each function inside the array takes two arguments. First argument represents error, which is null if there are no errors, second represents the result of the function, which is passed to the second argument of the async.parallel() function's callback.

Migrating from localStorage to chrome.storage

I'm in the process of migrating my Chrome extension's persistency repository, from localStorage to chrome.storage. An important difference between them is that chrome.storage is asynchronous, therefore a callback function needs to be passed.
How would you modify a loop that writes to localStorage, synchronously, to the async chrome.storage?
for (var i = 0; i < obj.length; i++) {
localStorage.setItem(obj[i].key, obj[i].val);
}
doThisWhenAllElementsAreSaved();
Thanks.
For this example, I'll use chrome.storage.local, but you can replace it with chrome.storage.sync if you prefer.
The goal is to use chrome.storage.local.set. The first step is to convert your obj into an object that contains the list of pair of key / value:
var keyValue = {};
for (var i = 0; i < obj.length; i++)
{
keyValue[obj[i].key] = obj[i].val;
}
Then we call the actual method:
chrome.storage.local.set(keyValue, function()
{
// Notify that we saved.
doThisWhenAllElementsAreSaved();
});
or, simply:
chrome.storage.local(keyValue, doThisWhenAllElementsAreSaved);
Note that the callback will be called on success, as well as on failure. If the storage failed, then chrome.runtime.lastError will be set.
You can save multiple items at once with the chrome.storage API, so I would use the following approach:
Before, using localStorage.setItem
for (var i = 0; i < obj.length; i++) {
localStorage.setItem(obj[i].key, obj[i].val);
}
doThisWhenAllElementsAreSaved();
After, using chrome.storage.local.set
var items = {};
for (var i = 0; i < obj.length; i++) {
items[obj[i].key] = obj[i].val;
}
chrome.storage.local.set(items, function() {
doThisWhenAllElementsAreSaved();
});
If you need to know whether the save operation succeeded, check the value of chrome.runtime.lastError in the callback.
The best practice would be just to change the code to use chrome.storage.local like others said.
But sometimes, it gets really messy and you may want some code to remain untouched.
I tried to use a library that highly incorporates window.localStorage in a packaged app environment(the library was angular-cached-resource) and didn't want to change the original source code. So I managed to make the following shim: https://github.com/stewartpark/chrome-localStorage-shim
Hope it helps! Happy hacking.

Resources