I want to add a wait time between each forEach statement in the below code, I don't want to run into problems with rate limits so I want to wait, say 1 second, between each time the forEach runs. Is this possible?
const Discord = require("discord.js");
const client = new Discord.Client();
const config = require("./config.json");
client.on("ready", () => {
console.log(`Bot has started, with ${client.users.size} users, in ${client.channels.size} channels of ${client.guilds.size} guilds.`);
});
client.on("message", async message => {
if(message.author.bot) return;
if(message.content.indexOf(config.prefix) !== 0) return;
const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
const command = args.shift().toLowerCase();
if(command === "addalltorole") {
process.setMaxListeners(n);
let role = message.guild.roles.find(r => r.name == 'TribeVerified')
if (!role) return message.channel.send(`**${message.author.username}**, role not found`)
var hasRoles = ["449032040147582977", "489252560532799488", "449032406444277760", "449032567988158465", "449032704122552320", "449032907332255759", "449033048374247426", "449033186413117441", "459119831183130645", "449033329946394645", "462285271505829909", "528059697257775106", "462061656852398090", "461635407893889037", "535632204026609665", "535632207222407168535637767242121216", "535637767242121216", "535637777388142593", "542049404270673942"];
message.guild.members.filter(member => member.roles.filter(r => hasRoles.includes(r.id)).size > 2).forEach(member => member.addRole(role));
message.channel.send(`**${message.author.username}**, role **${role.name}** was added to all applicable members`)
}
});
client.login(config.token);
I found this on stackoverflow elsewhere, not sure if it could be of use to implement what I'm trying to do?
var array = ['some', 'array', 'containing', 'words'];
var interval = 1000; // how much time should the delay between two iterations be (in milliseconds)?
array.forEach(function (el, index) {
setTimeout(function () {
console.log(el);
}, index * interval);
});
Thanks so much in advanced for any help!
Yes, that approach will work just fine.
It will space them 1 second apart regardless of how long they take to complete.
...and that's probably fine for what you are doing...
...but if you want you can use the fact that addRole returns a Promise to do some cool stuff.
For example, this uses reduce to pass along the Promises so that each call waits for the previous call to finish and then waits an additional second before making the next request:
message.guild.members.filter(member => member.roles.filter(r => hasRoles.includes(r.id)).size > 2)
.reduce(async (previous, member) => {
await previous; // wait for the previous call to finish
await new Promise(resolve => setTimeout(resolve, 1000)); // wait another second
return member.addRole(role); // pass this Promise to the next iteration
}, Promise.resolve()); // start with a resolved Promise
Here is a working demo you can run:
class Member {
constructor(name) { this.name = name; }
async addRole(role) {
await new Promise(resolve => setTimeout(resolve, 100)); // simulate asynchronous call
console.log(`added role "${role}" to ${this.name}`);
}
}
const members = [
new Member('first'),
new Member('second'),
new Member('third'),
new Member('fourth'),
new Member('fifth')
];
members.reduce(async (previous, member) => {
await previous;
await new Promise(resolve => setTimeout(resolve, 1000));
return member.addRole('admin');
}, Promise.resolve());
...but yes, either approach will work and should be just fine for what you are doing.
Related
I am using a chrome extension to activate infinite async instances of loops so they do not conflict with each other.
There is a list of values and one item is being passed to each individual loop. Those loops are executed in the content.js and are being managed by the background.js but they are initialized, started and cancelled from the popup.js.
Now big Question is how do I use best practices to make the management of multiple async loops as easy as possible?
Is there any possible way to also cancel these loops in an easy way?
example code:
content.js:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if(request.message.active){
console.log("do something");
dispatch(request.message);
}});
async function dispatch(alternator) {
if (alternator.active) {
await new Promise(resolve => setTimeout(resolve, alternator.timeout));
console.log("do something");
}
return;
}
This background.js should have a list of async loops to manage in an array or something easy to manage. The for-loop is consuming too much time and the timeout is causing too much load.
background.js
async function dispatchBackground() {
while (1) {
for (let i = 0; i < alternator.length; i++) {
if(alternator[i].active){
chrome.tabs.sendMessage(alternator[i].tab_id, {"message": alternator[i]});
}
}
await new Promise(resolve => setTimeout(resolve, 100));
}
return;
}
You should probably use a library.
...but that would be boring!
In the following 👇 code, macrotask uses requestIdleCallback to run a callback on a JS runtime macrotask.
The user supplies the data to process, the logic to run (synchronously) at each step, and the continuation condition; they do not have to worry about explicitly yielding to an API.
Function createTask constructs a generator to return steps until the continuationPredicate returns false. Generator functions enable us to suspend and resume synchronous code - which we need to do here to switch between tasks in a round-robin fashion. A more advanced solution could prioritise tasks according to a heuristic.
createCircularList returns a wrapper around an array that exposes add, remove, and next (get the next item in creation order or, if we are at the "end", loop around to the first item again).
createScheduler maintains the task list. While there are tasks remaining in the task list, this function will identify the next task, schedule its next step on a macrotask, and wait for that step to complete. If that was the final step in the current task, the task is then removed from the task list.
Note that the precise interleaving of the output of this code will depend on things like how busy your machine is. The intent of the demonstration is to show how the task queue can be added-to while it is being drained.
const log = console.log
const nop = () => void 0
const stepPerItem = (_, i, data) => i < data.length
const macrotask = (cb) => (...args) => new Promise((res) => (typeof requestIdleCallback ? requestIdleCallback : setTimeout)(() => res(cb(...args))))
const createTask = (data,
step,
continuePredicate = stepPerItem,
acc = null,
onDone = nop) =>
(function*(i = 0) {
while(continuePredicate(acc, i, data)) {
acc = step(acc, i, data)
yield [acc, onDone]
i++
}
return [acc, onDone]
})()
const createCircularList = (list = []) => {
const add = list.push.bind(list)
const remove = (t) => list.splice(list.indexOf(t), 1)
const nextIndex = (curr, currIndex = list.indexOf(curr)) =>
(currIndex === list.length - 1) ? 0 : currIndex + 1
const next = (curr) =>
list.length ? list[nextIndex(curr)] : null
return { add, remove, next }
}
const createScheduler = (tasks = createCircularList()) => {
let isRunning = false
const add = (...tasksToAdd) =>
(tasksToAdd.forEach((t) => tasks.add(t)),
!isRunning && (isRunning = true, go()))
const remove = tasks.remove.bind(tasks)
const go = async (t = null) => {
while(t = tasks.next(t))
await macrotask(({ done, value: [result, onDone] } = t.next()) =>
done && (tasks.remove(t), onDone(result)))()
isRunning = false
}
return { add, remove }
}
const scheduler = createScheduler()
const task1 = createTask([...Array(5)], (_, i) => log('task1', i))
const task2 = createTask([...Array(5)], (_, i) => log('task2', i))
const task3 = createTask([...Array(5)], (_, i) => log('task3', i))
scheduler.add(task1, task2)
setTimeout(() => scheduler.add(task3), 50) // you may need to fiddle with the `setTimeout` delay here to observe meaningful interleaving
I want to upload 20 photos to imgur at once. But I want to do in certain time gap between each upload. I was using Promise.all([...listof20promises]). But now I want to do api calls in interval of 1 second, and also I should be able to get response in the form of .then((responseArray)=>...) like we get in Promise.all.
How can I achieve it?
Use async/await and a normal loop with a delay between each iterations, for example:
const delay = t => new Promise(resolve => setTimeout(resolve, t));
const uploadAll = async files => {
const results = [];
for (const file in files) {
if (results.length) {
await delay(1000); // 1 second
}
const response = await uploadFile(file);
results.push({ file, response });
}
return results;
}
And use it as
uploadAll([ file1, file2, file3, ... ]).then(results => {
// results[0] = { file, response } where file === file1
// results[1] = { file, response } where file === file2
// ...
});
You can do as follows:
async function upload(files) {
while (files.length) {
await Promise.all(files.splice(0, 20));
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
upload([...]);
Remember that splice will mutate your array. If you don't want that, you can use slice like below:
async function upload(files) {
let i = 0;
while (i < files.length) {
await Promise.all(files.slice(i, i += 20));
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
upload([...]);
If you want the delay to be between one upload start to the next upload start, you can map the files, use a delay promise (taken from Yanick Rochon's answer) with the index of the current file * 1000 as the timeout, and then call the upload:
const delay = t => new Promise(resolve => setTimeout(resolve, t));
const uploadAll = files => Promise.all(
files.map((file, i) =>
delay(i * 1000).then(() => uploadFile(file))
)
);
The main difference between Yanick Rochon's answer and mine, is that in his answer the next upload would start 1 second after the previous call ended, while this code would dispatch the calls within 1 second of each other, even if the previous call is still pending.
I have a database setup with NodeJS and want to wait until certain table is created before start to create any others. This method tableExists resolves with the status being either true/false, but I want it to wait until it's true only.
const checkTableExists = async () => {
const exists = await queryInterface.tableExists('Subjects');
return exists;
}
How can I force a wait until checkTableExists returns true?
Using setTimeout:
const CHECK_INTERVAL = 200; // every 200ms
const checkTableExists = async () => {
const exists = await queryInterface.tableExists('Subjects');
if (!exists) {
return new Promise((resolve, reject) => {
setTimeout(() => checkTableExists().then(resolve).catch(reject), CHECK_INTERVAL);
});
}
return exists;
}
The solution to something like this is not to keep on waiting. There are other issues that may cause the table not to be created. You may want to adjust the above code to stop checking after it has checked for set number of times, or a duration has passed. Use something reasonable, depending on the environment where your db is running.
Add a delay and repeat:
// Utility function
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const checkTableExists = async () => {
while (true) {
const exists = await queryInterface.tableExists('Subjects');
if (exists) return true;
await delay(10000); // Wait 10 seconds before trying again.
}
}
Although this resolves the promise with true, it is actually is not necessary to return a boolean, as the resolving promise is enough as a signal that the table now exists -- true is the only possible outcome when the promise resolves.
I'm currently developing a program where I need to use Promise.all to run a few functions at the same time. But before I can continue with the task, I need ONLY 2 of the promises to finish and then run the .then(), how would I go about doing this?
Example:
await Promise.all([Task1(),Task2(),Task3(),Task4(),Task5()]);
I need it to continue the code when only (for example) Task1 and Task 4 have finished.
I have tried to experiment by using a while loop waiting for Task1 and Task2 finish by settings variables on the finish, but that. doesn't work at all.
In a comment you seem to have said you specifically know that in advance which two are more urgent than the rest (and that in this example, it's Task1 and Task4).
Then just use Promise.all twice:
const allResults = Promise.all([
Promise.all([Task1(), Task4()])
.then(([result1, result4]) => {
// Those two are done, do what you like with `result1` and `result4`...
return [result1, result4];
}),
Task2(),
Task3(),
Task5()
])
.then(([[result1, result4], result2, result3, result5]) => {
// All five are done now, let's put them in order
return [result1, result2, result3, result4, result5];
})
.then(/*...*/)
.catch(/*...*/);
In there, I've preserved the overall 1, 2, 3, 4, 5 order in the outer chain by remapping the order in the overall then handler.
Originally, I assumed you wanted to wait until any two have finished, rather than a specific two. There's no built-in for that, but it's easy enough to write:
function enough(promises, min) {
if (typeof min !== "number") {
return Promise.all(promises);
}
let counter = 0;
const results = [];
return new Promise((resolve, reject) => {
let index = 0;
for (const promise of promises) {
let position = index++;
promise.then(
result => {
results[position] = result;
if (++counter >= min) {
resolve(results);
}
},
reject
);
}
});
}
Live Example:
function enough(promises, min) {
if (typeof min !== "number") {
return Promise.all(promises);
}
let counter = 0;
const results = [];
return new Promise((resolve, reject) => {
let index = 0;
for (const promise of promises) {
let position = index++;
promise.then(
result => {
results[position] = result;
if (++counter >= min) {
resolve(results);
}
},
reject
);
}
});
}
const delay = (ms, ...args) => new Promise(resolve => setTimeout(resolve, ms, ...args));
const rnd = () => Math.random() * 1000;
enough(
[
delay(rnd(), "a"),
delay(rnd(), "b"),
delay(rnd(), "c"),
delay(rnd(), "d"),
delay(rnd(), "e")
],
2
)
.then(results => {
console.log(results);
})
.catch(error => {
console.error(error);
});
One way to do it is by constructing a new array of the randomized promises, and then wait for those only:
let array = [Task1(),Task2(),Task3(),Task4(),Task5()];
// Select any two promises after running the randomization logic
let promises = Promise.all[array[1], array[3]];
promises
.then(() => {
// Do stuff here
});
I've seen tricks like this :
Promise.all(promises.map(p => p.catch(() => undefined)));
Quite unsafe though.
Original answer : here
OK, as I understand you, you want to do something like
const importantOnes = [Task1(), Task2()];
const remainingOnes = [Task3(), Task4(), Task5()];
const priorityPromise = Promise.all(importantOnes);
priorityPromise.then(doPriorityStuff);
Promise.all([priorityPromise, ...remainingOnes]).then(processTheCompleteData);
Promise.all() does not run your tasks simultaneously, it only awaits all Promises to resolve before resolving the returned Promise.
Your tasks will be run as soon as you create each Promise.
If you want to wait after specific tasks, only include those tasks in Promise.all:
const tasks = [Task2(), Task3(), Task5()];
const result1 = await Promise.all([Task1(), Task4()]);
// Task1 and Task4 are done here
const result2 = await Promise.all(tasks);
// All remaining tasks are done here
I want to test how much requests i can do and get their total time elapsed. My Promise function
async execQuery(response, query) {
let request = new SQL.Request();
return new Promise((resolve, reject) => {
request.query(query, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
And my api
app.get('/api/bookings/:uid', (req, res) => {
let st = new stopwatch();
let id = req.params.uid;
let query = `SELECT * FROM booking.TransactionDetails WHERE UID='${id}'`;
for (let i = 0; i < 10000; i++) {
st.start();
db.execQuery(res, query);
}
});
I can't stop the for loop since its async but I also don't know how can I stop executing other calls after the one which first rejects so i can get the counter and the elapsed time of all successful promises. How can i achieve that?
You can easily create a composable wrapper for this, or a subclass:
Inheritance:
class TimedPromise extends Promise {
constructor(executor) {
this.startTime = performance.now(); // or Date.now
super(executor);
let end = () => this.endTime = performance.now();
this.then(end, end); // replace with finally when available
}
get time() {
return this.startTime - this.endTime; // time in milliseconds it took
}
}
Then you can use methods like:
TimedPromise.all(promises);
TimedPromise.race(promises);
var foo = new TimedPromise(resolve => setTimeout(resolve, 100);
let res = await foo;
console.log(foo.time); // how long foo took
Plus then chaining would work, async functions won't (since they always return native promises).
Composition:
function time(promise) {
var startTime = performance.now(), endTime;
let end = () => endTime = performance.now();
promise.then(end, end); // replace with finally when appropriate.
return () => startTime - endTime;
}
Then usage is:
var foo = new Promise(resolve => setTimeout(resolve, 100);
var timed = time(foo);
await foo;
console.log(timed()); // how long foo took
This has the advantage of working everywhere, but the disadvantage of manually having to time every promise. I prefer this approach for its explicitness and arguably nicer design.
As a caveat, since a rejection handler is attached, you have to be 100% sure you're adding your own .catch or then handler since otherwise the error will not log to the console.
Wouldn't this work in your promise ?
new Promise((resolve, reject) => {
var time = Date.now();
request.query(query, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
}).then(function(r){
//code
}).catch(function(e){
console.log('it took : ', Date.now() - time);
});
Or put the .then and .catch after your db.execQuery() call
You made 2 comments that would indicate you want to stop all on going queries when a promise fails but fail to mention what SQL is and if request.query is something that you can cancel.
In your for loop you already ran all the request.query statements, if you want to run only one query and then the other you have to do request.query(query).then(-=>request.query(query)).then... but it'll take longer because you don't start them all at once.
Here is code that would tell you how long all the queries took but I think you should tell us what SQL is so we could figure out how to set connection pooling and caching (probably the biggest performance gainer).
//removed the async, this function does not await anything
// so there is no need for async
//removed initializing request, you can re use the one created in
// the run function, that may shave some time off total runtime
// but not sure if request can share connections (in that case)
// it's better to create a couple and pass them along as their
// connection becomes available (connection pooling)
const execQuery = (response, query, request) =>
new Promise(
(resolve, reject) =>
request.query(
query
,(error, result) =>
(error)
? reject(error)
: resolve(result)
)
);
// save failed queries and resolve them with Fail object
const Fail = function(detail){this.detail=detail;};
// let request = new SQL.Request();
const run = (numberOfTimes) => {
const start = new Date().getTime();
const request = new SQL.Request();
Promise.all(
(x=>{
for (let i = 0; i < numberOfTimes; i++) {
let query = `SELECT * FROM booking.TransactionDetails WHERE UID='${i}'`;
db.execQuery(res, query, request)
.then(
x=>[x,query]
,err=>[err,query]
)
}
})()//IIFE creating array of promises
)
.then(
results => {
const totalRuntime = new Date().getTime()-start;
const failed = results.filter(r=>(r&&r.constructor)===Fail);
console.log(`Total runtime in ms:${totalRuntime}
Failed:${failed.length}
Succeeded:${results.length-failed.length}`);
}
)
};
//start the whole thing with:
run(10000);