I have an array of objects, and for each object inside the array I want to call an asynchronous function.
However, I should only send 20 requests in a minute.
Example:
const myArray = ["a","b","aa","c","fd","w"...]
myArray length is 60.
I used to split myArray into sub-arrays of length 20 each and iterate over each sub-array and call the async function.
Then wait for a minute and then iterate over the next sub-array and so on. However I was doing that manually in a way such as
let promise;
let allPromises = [];
// Iterate over subArray1 and call the function for each element
for(let i = 0 ; i < subArray1.length ; i++){
promise = await myAsyncFunc(subArray1[i]);
allPromises.push(promise);
}
// wait for all the async functions to get resolved using Promise.all()
await Promise.all(allPromises);
allPromises = [];
await new Promise((resolve) => setTimeout(resolve, 60000));
if(subArray2){
// Iterate over subArray2 and call the function for each element
for(let i = 0 ; i < subArray2.length ; i++){
promise = await myAsyncFunc(subArray2[i]);
allPromises.push(promise);
}
// wait for all the async functions to get resolved using Promise.all()
await Promise.all(allPromises);
allPromises = [];
await new Promise((resolve) => setTimeout(resolve, 60000));
}
if(subArray3){
// Iterate over subArray3 and call the function for each element
for(let i = 0 ; i < subArray3.length ; i++){
promise = await myAsyncFunc(subArray3[i]);
allPromises.push(promise);
}
// wait for all the async functions to get resolved using Promise.all()
await Promise.all(allPromises);
allPromises = [];
await new Promise((resolve) => setTimeout(resolve, 60000));
}
If my array got more than 100 elements that would be an issue.
Can anyone provide a practical way to handle this situation?
A naive way to handle this would be to batch requests and wait for a batch to finish, wait a minute and launch the next batch.
Another way is to batch the first 20 requests, then everytime we get a response, we schedule the next fetch in 60s so we have always have 20 requests in a sliding window of 60 seconds.
Here is how both approaches could be implemented
const MAX_REQUESTS = 20;
// let's assume you have a fetchData function that takes dataRequestInfo as a param and builds the fetch from there
async function fetchData(dataRequestInfo) {
// do your fetch here, process the data and return your model
}
/****** naive batch approach ******/
async function batchRequests(dataRequestInfoList) {
const batchSize = MAX_REQUESTS;
const batches = splitToBatches(dataRequestInfoList, batchSize);
const results = [];
for (let batch of batches) {
const start = Date.now();
const batchResults = await Promise.all(batch.map(fetchData));
results.push(...batchResults);
// wait for 1 minutes before next batch
await new Promise((resolve) => setTimeout(resolve, 60 * 1000));
// alternatively wait for 1 minutes since the first fetch
// const duration = Date.now() - start;
// await new Promise(resolve => setTimeout(resolve, 60 * 1000 - duration));
// do something with batchResults if needed
}
return results;
}
function splitToBatches(arr, batchSize) {
const batches = [];
const lastIndex = arr.length - 1;
let index = 0;
while (index <= lastIndex) {
const nextIndex = index + batchSize;
batches.push(arr.slice(index, nextIndex));
// in case the last element is in an isolated batch we exit the loop
if (index === lastIndex) {
break;
}
index = Math.min(lastIndex, nextIndex);
}
return batches;
}
/****** batch then queue requests ******/
async function batchThenQueueRequests(dataRequestInfoList) {
return new Promise(resolve => {
let currentIndex = MAX_REQUESTS;
let processedRequests = 0;
const results = [];
const fetchNext = async (result) => {
processedRequests++;
results.push(result)
if (processedRequests.length === dataRequestInfoList.length) {
// everything was processed let's resolve the promise
resolve(results);
}
if (currentIndex >= dataRequestInfoList.length) {
// no more data to fetch
return;
};
// wait for 1 minute before next request
await new Promise(resolve => setTimeout(resolve, 60 * 1000));
fetchData(dataRequestInfoList[currentIndex]).then(fetchNext);
currentIndex++;
}
for (let i = 0; i < MAX_REQUESTS; i++) {
fetchData(dataRequestInfoList[i]).then(fetchNext);
}
});
}
Your call of Promise.all is not necessary as by the time it executes, all the promises of the preceding loop have already resolved. promises is not an array of promises, but an array of resolution values.
You should have your values in one array, like you presented myArray, and then you can just perform a for..of loop:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const myArray = [...subArray1, ...subArray2, ...subArray3];
for (const value of myArray) {
await myAsyncFunc(value);
await delay(3000); // 3 second cooldown
}
There is no need to chunk a long array into subarrays of at most 20 elements, since this code inserts a cooldown of 3 seconds after each request, so it is guaranteed that you'll not have more than 20 requests per minute.
If the time to resolve myAsyncFunc() is significant compared to those 3 seconds, then you would execute considerably fewer requests per minute. In that case you can start the timer for 3 seconds at the same time as you initiate the request:
for (const value of myArray) {
await Promise.all(myAsyncFunc(value), delay(3030));
}
The delay is here intentionally a bit bigger than 3 seconds, so to take no risk of being too close to 20/minute.
Related
I have a few hundreds of things to render in parallel in an html5 canvas. These are drawn in parallel in a Promise.all call. Now, I would like to know which of these promise is the last to be resolved.
// get a promise that will resolve in between 0 and 5 seconds.
function resolveAfterSomeTime(): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, Math.random() * 5000));
}
const myPromises = [];
for (let i = 0; i < 100; i++) {
myPromises.push(resolveAfterSomeTime);
}
Promise.all(myPromises).then(() => {
// find out which promise was the last to resolve.
})
In my case, I have multiple classes with each a render() function. Some of these are heavier than others, but I want to know which ones.
I have something along these lines, and I would like to know which promise is the slowest to resolve, so that I can optimise it.
The best way I can think of is to use a counter indicating the number of promises that have resolved so far:
function resolveAfterSomeTime() {
return new Promise((resolve) => setTimeout(resolve, Math.random() * 5000));
}
const myPromises = [];
let resolveCount = 0;
for (let i = 0; i < 100; i++) {
myPromises.push(
resolveAfterSomeTime()
.then(() => {
resolveCount++;
if (resolveCount === 100) {
console.log('all resolved');
console.log('array item', i, 'took longest');
}
})
);
}
Here's a way where each promise sets the value of lastPromiseToResolve after resolving. The last promise to resolve would set it last.
// get a promise that will resolve in between 0 and 5 seconds.
function resolveAfterSomeTime(): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, Math.random() * 5000));
}
let lastPromiseToResolve = null
const myPromises = [];
for (let i = 0; i < 100; i++) {
const promise = resolveAfterSomeTime()
myPromises.push(promise.then(() => {
lastPromiseToResolve = promise // all the promises will set lastPromiseToResolve
}));
}
Promise.all(myPromises).then(() => {
console.log(lastPromiseToResolve) // this would be the last promise to resolve
})
You could time each promise. You could even assign an identifier to each one if you want to know specifically which is resolving. The timePromise function below takes an id and a function that returns a promise, times that promise, and logs the result. It doesn't change the result of the promises, so you can use myPromises as you normally would.
function resolveAfterSomeTime() {
return new Promise((resolve) => setTimeout(resolve, Math.random() * 1000));
}
// f is a function that returns a promise
function timePromise(id, f) {
const start = Date.now()
return f()
.then(x => {
const stop = Date.now()
console.log({id, start, stop, duration: (stop - start)})
return x
})
}
const myPromises = [];
for (let i = 0; i < 100; i++) {
myPromises.push(timePromise(i, resolveAfterSomeTime));
}
Promise.all(myPromises).then(() => {
// find out which promise was the last to resolve.
})
I'm not sure how you're creating your array of promises in your actual code, so it might not be straightforward to wrap each promise in a function that returns it. But you could likely adapt this to work with your situation.
If you aren't concerned with knowing exactly how long each takes, you could just have timePromise take a promise that's already started, and time from when timePromise is called to when it resolves. This wouldn't be as accurate, but would still give you a general idea, especially if one or a few promises are taking much longer than others.
Something like this:
function timePromise(id, p) {
const start = Date.now()
return p
.then(x => {
const stop = Date.now()
console.log({id, start, stop, duration: (stop - start)})
return x
})
}
const myPromises = [];
for (let i = 0; i < 100; i++) {
myPromises.push(timePromise(i, resolveAfterSomeTime()));
}
I have a list of values that I wanna return asynchronously, but a limited number of values at a time.
So I call my function once and it has an array of say 20 distinct elements say [1,2,...,20], and it keeps on returning 5 elements every second. So I want:
[1,2,..,5] at 0 sec,
[6,7,..,10] at 1 sec,
and so on....
I was thinking on using buffer files. If it is a valid solution, can someone tell me how to implement it for the given example?
An async generator function (or method) can yield (loosely, return) a sequence of values over time. Here's an example:
function sleepRandom(maxMs) {
return new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * maxMs)));
}
async function *example(max) {
yield -1; // Example of explicit value
// Wait 500ms
await sleepRandom(800);
// Example of loop
for (let i = 0; i < max; ++i) {
// Wait 500ms
await sleepRandom(800);
// Yield current loop value
yield i;
}
// All done
yield "Done";
}
(async () => {
for await (let value of example(10)) {
console.log(value);
}
})()
.catch(error => {
console.error(error);
});
The * after function is what makes it a generator function. The async before function is what makes it async.
You could use nodes EventEmitter for that:
var events = require('events');
function spitChunks(arr, chunksize = 5, delay = 1000, em = null) {
if(!em) em = new events.EventEmitter();
if(arr.length > 0) setTimeout(() => {
em.emit('nextChunk', arr.splice(0,chunksize));
spitChunks(arr, chunksize, delay, em);
}, delay);
return em;
}
let numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
let emitter = spitChunks(numbers);
emitter.on('nextChunk', data => console.log(data));
So I have an array that has a list of Channel Names that I would like to join (This is for a Twitch Chat Bot), the API endpoint for joining a channel has a rate limit of 50 joins per 15 seconds. I am trying to figure out the best way to iterate through 50 channel names, pause for 15 seconds, then resume iterating through the rest of the array, pausing for 15 seconds every 50 names.
I had originally tried a generic for loop using a fake, 100 value array, a Modulus Operator, and setTimeout, but to no avail. But in all honesty, I didn't know where to start so it is quite bad.
let array = ([...Array(100).keys()].map(x => ++x))
for (var i = 1; i < array.length; i++) {
if (i % 50 === 0) {
setTimeout(() => {
console.log('Waiting 15 seconds.')
}, 15000);
} else {
console.log(array[i])
}
}
Ideally, it would log 1-50, then wait 15 seconds, then log 51-100.
You can use async and await to pause your for loop in a fairly simple fashion:
// utility function that returns a promise that resolves after t milliseconds
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
async function processArray(array) {
for (let i = 1; i < array.length; i++) {
if (i % 50 === 0) {
await delay(15 * 1000);
}
console.log(array[i])
}
}
let data = ([...Array(100).keys()].map(x => ++x))
processArray(data).then(() => {
console.log("all done");
});
FYI, I don't quite understand why you're trying to use index 1 through 100 on a 100 element array. I think you should be using indexes 0 through 99 for a 100 element array. I left the code that way you had in under the assumption that maybe you're doing this on purpose.
const _ = require('lodash');
Use iterators/generators so that you can control when you want the next item instead of fighting to "stop" the execution of the loop.
function* myIterator(data) {
for (const item of data)
yield item;
}
Then set up a function that will do the actual execution, taking the iterator as a parameter. This way, each time you call it, you can pass in the iterator so that it remembers where it left off.
function execute(it) {
// We want to do this in batches of 50
// (but test with lower value to better see how it works)
_.range(0, 50).forEach(() => {
// Get next item
const item = it.next();
// If no more items, we're done
if (item.done) return;
else {
// Do something with the item
console.log(item.value);
};
});
// Pause for 15 seconds and then continue execution
setTimeout(() => execute(it), 15000);
}
Create your data, generate an iterator from it and then execute.
(function main() {
const data = _.range(1, 101);
const it = myIterator(data);
execute(it);
})();
Try this
let array = ([...Array(100).keys()].map(x => ++x))
const startLoop = currentIndex => {
for (let i = currentIndex; i < array.length; i++) {
if (i % 50 === 0) {
setTimeout(() => {
console.log('Waiting 15 seconds.');
startLoop(i + 1);
}, 15000)
break;
}
console.log(array[i])
}
}
startLoop(1);
Writing a recursive loop function from scratch (at the cost of performance) is probably the simplest solution, but you can accomplish this iteratively using a while loop and a Promise, without compromising performance.
In the code below, every time the 1-based index of the loop reaches a multiple of batch_size, an await is called which stops execution until the Promise resolves. The promise is just a setTimeout call, which waits for pause_ms before allowing the loop to continue. The values are slightly different here just to make testing easier; you can change them freely to meet your needs.
const vals = [...new Array(20)].map(() => Math.floor(Math.random() * 9) + 1);
console.log(vals);
async function iterateBatches(arr, batch_size, pause_ms) {
// Create a promise generator that will automatically resolve after the desired number of millseconds.
const wait = () => new Promise(r => {
setTimeout(r, pause_ms)
});
// Establish the starting and ending points for the iteration.
const len = arr.length;
let i = 0;
// As long as the loop hasn't reached the final element,
while (i < len) {
// Perform an operation with the value in your array.
console.log(i, vals[i]);
// If we've reached the end of the array, break out of the loop to prevent an unneeded iteration.
if (i >= len - 1) break;
// Increment the index (this is also equal to the 1-based index, which saves us some lines).
// If the 1-based index is a multiple of batch_size and we're not on the first iteration, wait for our promise generator.
if (++i % batch_size === 0 && i > 0) await wait();
}
console.log("Done!");
}
iterateBatches(vals, 5, 2000);
You could just create the loop manually. Here's a simplified example...
var array = ['a','b','c','d','e','f'];
var i = 0;
function loop(){
if(i>=array.length) return;
if(i==3){
setTimeout(function(){
i++;
loop();
},15000);
return;
}
i++;
console.log(array[i]);
loop();
}
loop();
I have this code snippet that should await 5 promises, pause for 2 seconds and then continue to await another 5. However, the output shows that the two Promise.all are not awaited although when I tried to return a value in the promise it returns properly. Am I understanding the concept of Promise.all wrong or there is something missing in my code?
(function () {
let val = 0
const promiseArr = []
for (let i = 0; i < 10; i++) {
promiseArr[i] = new Promise((res) => {
val += 500
setTimeout((val2) => {
console.log(val2)
res()
}, val, val)
})
}
console.log();
(async function() {
await Promise.all(promiseArr.slice(0, 5))
console.log('start await')
await new Promise((res) => setTimeout(res, 2000))
console.log('done await')
await Promise.all(promiseArr.slice(6, 10))
})()
}) ()
Expected output:
500
...
2500
start await
done await
3000
...
5000
Actual output:
500
1000
1500
2000
2500
start await
3000
3500
4000
4500
done await
5000
EDIT:
Now I understand why it behaves like that. Looks like I misunderstood a fundamental concept with Promises. Thanks for you guys help!
You're seeing this because the promises begin running as soon as they are created (or, well, as soon as the current synchronous block finishes anyway), not when they are awaited upon.
Instrumenting and reorganizing your code a little, to print timestamps for each event might illuminate things:
const t0 = +new Date();
const logWithTime = (message = "") =>
console.log("" + Math.round(+new Date() - t0) + ": " + message);
(async function() {
let val = 0;
const resolver = res => {
val += 500;
const thisVal = val; // so we have a binding for the effective value
logWithTime("Creating timeout for " + thisVal);
setTimeout(
arg => {
logWithTime("Timeout for " + thisVal);
res();
},
thisVal,
thisVal,
);
};
const promiseArr = [];
for (let i = 0; i < 10; i++) {
promiseArr[i] = new Promise(resolver);
}
logWithTime("awaiting for first 5...");
await Promise.all(promiseArr.slice(0, 5));
logWithTime("waiting for 2 seconds...");
await new Promise(res => setTimeout(res, 2000));
logWithTime("waiting for the rest...");
await Promise.all(promiseArr.slice(6, 10));
logWithTime("all done");
})();
prints
0: Creating timeout for 500
10: Creating timeout for 1000
10: Creating timeout for 1500
10: Creating timeout for 2000
10: Creating timeout for 2500
10: Creating timeout for 3000
10: Creating timeout for 3500
10: Creating timeout for 4000
10: Creating timeout for 4500
10: Creating timeout for 5000
10: awaiting for first 5...
511: Timeout for 500
1011: Timeout for 1000
1515: Timeout for 1500
2013: Timeout for 2000
2510: Timeout for 2500
2511: waiting for 2 seconds...
3010: Timeout for 3000
3512: Timeout for 3500
4011: Timeout for 4000
4511: Timeout for 4500
4511: waiting for the rest...
5011: Timeout for 5000
5011: all done
(or thereabouts, depending on things).
You can see the order in which the timeouts get resolved there.
Your timers start ticking when you call setTimeout, not when you await the promise that is resolved by them.
While (res) => setTimeout(res, 2000) runs, you have 4 prior setTimeouts which finish and call console.log before resolving.
I stumbled on this misunderstanding myself too: when you define a Promise, it's function is executed in that moment. If I understand what you're trying to do, you need to define an array of functions, and call them as needed.
(function () {
let val = 0
const promiseArr = []
for (let i = 0; i < 10; i++) {
promiseArr[i] = () => new Promise((res) => {
val += 500
setTimeout((val2) => {
console.log(val2)
res()
}, val, val)
})
}
console.log();
(async function() {
await Promise.all(promiseArr.slice(0, 5).map((fn) => fn()))
console.log('start await')
await new Promise((res) => setTimeout(res, 2000))
console.log('done await')
await Promise.all(promiseArr.slice(6, 10).map((fn) => fn()))
})()
}) ()
I have a promise function which successfully can return a random integer between a min and max value.
const getRandomValue = (min = 0, max = 1) => {
return new Promise(function(resolve, reject){
let randomInteger = Math.floor(Math.random() * (max - min)) + min;
resolve(randomInteger);
});
};
I am trying to asychronously fill an array using this promise function, with 4 random integers.
The chronology of the calls is a challenge for me and I am brand new to this type of programming. Below is what I have been trying to get to work without success.
const getFourRandomValues = (min, max) => {
let randomIntegerArray = [];
for(let i = 0; i < 4; i++){
getRandomValue(20,30).then(x => console.log(x));
getRandomValue(20,30).then(x => {
randomIntegerArray.push(x)
});
}
return randomIntegerArray;
};
console.log(getFourRandomValues());
As you experienced programers surely know, it yields:
[]
22
29
25
21
All help is much appreciated
Notice, that if you execute asynchronous operations, the will be resolved or rejected only after your synchronous code, that's why in your example you get an empty array. You can easily avoid it using Promise.all, I recommend it for your case because you're using multiple asynchronous calls.
The logic is the following:
Push every asynchronous call into array.
Call Promise.all(arrayOfPromises)
Handle the result via .then() block (inside then you have an access to the result of every call if each of them was resolved successfully)
Here is an example:
const getRandomValue = (min = 0, max = 1) => {
return new Promise(function(resolve, reject){
let randomInteger = Math.floor(Math.random() * (max - min)) + min;
resolve(randomInteger);
});
};
const getFourRandomValues = (min, max) => {
let promises = [];
for(let i = 0; i < 4; i++) {
promises.push(getRandomValue(20,30));
}
return Promise.all(promises);
}
getFourRandomValues()
.then(x => console.log(x));
Your getFourRandomValues method is doing an async operation. Since then, it should be an async function as well. The [] is returned in your console because you are trying to output the array before the promises inside it has been resolved.
To fix it, you can change getFourRandomValues method to return a promise, that will be resolved when the array is ready:
const getFourRandomValues = async(min, max) => {
let randomIntegerArray = [];
for(let i = 0; i < 4; i++){
const randomValue = await getRandomValue(20,30);
randomIntegerArray.push(randomValue)
}
return randomIntegerArray;
};
getFourRandomValues().then((data) => {
console.log(data);
});
In this example I used async function for better code readability.