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()));
}
Related
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.
Basically I have two async functions. One of them is a simple 5 second timeout and the other is a complicated async function with multiple steps.
Here is an example
const delay = ms => new Promise(res => setTimeout(res, ms));
class Runner {
async start() {
let printStuff = async () => {
for(let i = 0 ; i < 50; i++){
console.log(i);
await delay(50);
}
}
let printLetters = new Promise(async function(resolve, reject) {
const length = Math.floor(Math.random() * 10)
//dont know how long this will take
for(let i = 0; i < length; i++){
await printStuff();
}
resolve('letters');
});
let timeout = new Promise(async function(resolve, reject) {
await delay(5000);
resolve('timeout')
});
const finished = await Promise.all([timeout, printLetters]);
if(finished === 'timeout'){
this.stop();
}
}
stop(){
//stop printing stuff instantly
}
}
const myRunner = new Runner();
myRunner.start();
//could call myRunner.stop(); if the user canceled it
The way I would implement this would add a global variable and include an if statement inside the for loop to check if the interrupt has been called but I am wondering if there is a better way to implement this. An issue with this solution would be it would print a few more numbers. I would have to add another check to the other for loop and this could get messy quickly.
Here is a simple demo that uses my own library.
import { CPromise } from "c-promise2";
const task = CPromise.promisify(function* () {
let printStuff = CPromise.promisify(function* () {
for (let i = 0; i < 10; i++) {
console.log(i);
yield CPromise.delay(100);
}
});
const length = Math.floor(Math.random() * 10) + 3;
//dont know how long this will take
for (let i = 0; i < length; i++) {
yield printStuff();
}
return "letters";
});
const promise = task()
.timeout(5000)
.then(
(result) => console.log(`Done: ${result}`),
(err) => console.warn(`Fail: ${err}`)
);
setTimeout(() => {
promise.cancel();
}, 2000);
Is this what you're after?
What changed:
Promise.all replaced with Promise.race
added isStopped prop which makes the "complicated async function with multiple steps" skip execution for the remaining steps. it doesn't kill it, though. Promises are not cancelable.
const delay = ms => new Promise(res => setTimeout(res, ms));
class Runner {
isStopped = false;
async start() {
const printStuff = async () => {
let i = 0;
while (!this.isStopped) {
console.log(i++);
await delay(50);
}
}
const printLetters = new Promise(
resolve => printStuff()
.then(() => resolve('letters'))
);
const timeout = new Promise(
resolve => delay(5000)
.then(() => resolve('timeout'))
);
const finished = await Promise.race([timeout, printLetters]);
console.log({ finished });
if (finished === 'timeout') {
this.stop();
}
}
stop() {
this.isStopped = true;
}
}
const myRunner = new Runner();
myRunner.start();
<button onclick="myRunner.stop()">stop</button>
Initial answer (left it in as the comments reference it, not what's above; and in case someone finds it useful in 2074):
Here's an example outlining what I was suggesting in the comment. run() below returns a race between a rejector happening after 1s and a fulfiller which resolves in random time between 0 and 2s.
const rejector = (timeout) => new Promise((resolve, reject) => {
setTimeout(reject, timeout, 'rejected')
});
class Runner {
run() {
return Promise.race([
rejector(1000),
new Promise((resolve, reject) => {
setTimeout(resolve, Math.random() * 2000, 'fulfilled')
})
])
}
}
const t0 = performance.now();
[...Array(6).fill()].forEach((_, key) => {
const runner = new Runner();
runner.run()
.then(r => console.log(`Proomise ${key} ${r} after ${performance.now() - t0}ms`))
.catch(err => console.log(`Promise ${key} ${err} after ${performance.now() - t0}ms`));
})
Note: initially I placed the rejector inside the class but (at least for the above example) I don't see why it should not stay outside (in a real case scenario, imported from a helper file).
If you require an instantaneous stop capability, you would probably want to execute the print job as a external script. Then you use child processes like this.
const { spawn } = require('child_process');
class Runner {
......
start() {
this.job[somejobId] = spawn('command to execute script');
//this can be anything, including a node script, e.g. `node start.js`
.....
}
stop(jobId) {
if (jobId) {
//this will kill the script that you spawn above
this.job[jobId].kill('SIGHUP');
}
}
stopAllJobs() {
// looping through the job queue to kill all the jobs
this.job.forEach(job => job.kill('SIGHUP'))
}
}
You will have more info on how to start a child process from the node doc website https://nodejs.org/api/child_process.html#subprocesskillsignal
If your job (external script) is stalling, it's recommended that you only use the above codes if you have a minimum 2 CPU core, else it will affect your main process if your script is heavy.
let cancelDownload = true
const delay = () => {
return new Promise(() => {
if (cancelDownload === true){
setTimeout(() => {
console.log('Delaying...');
delay();
}, 1000);
}
else return null;
});
};
const cancelJob = async() => {
for(let i = 6; i>0; i--){
console.log('inside for ',i);
setTimeout(()=>{
cancelDownload = false
},4000)
await delay()
console.log('aaaaaaaa');
console.log(`the number is ${i}`);
}
}
cancelJob()
I am trying to write a delay function whereby once the condition is met delay is removed and all code is resumed but it seems that once the delay is done the code exits without executing the last two console logs
No recursion needed. Instead, you can:
Use setInteval to check your condition every second.
When the condition is correct, you need to resolve the promise.
Use clearInterval.
let cancelDownload = true
const delay = () => {
let intervalId;
return new Promise((resolve, reject) => {
const check = () => {
if (cancelDownload === true){
console.log('Delaying...');
} else {
clearInterval(intervalId);
resolve();
}
}
//check immediately
check();
//then check every second afterwards
intervalId = setInterval(check, 1000);
});
};
const cancelJob = async() => {
for(let i = 6; i>0; i--){
console.log('inside for ',i);
setTimeout(()=>{
cancelDownload = false
},4000)
await delay()
console.log('aaaaaaaa');
console.log(`the number is ${i}`);
}
}
cancelJob()
This can be generalised a bit in the following fashion - instead of hard-coding the condition, supply it as a callback. Then you can have a delay function without using global variables and it can wait for different things, not just one single variable.
const delayWhile = shouldWait => () => {
let intervalId;
return new Promise((resolve, reject) => {
const check = () => {
if (shouldWait()){
console.log('Delaying...');
} else {
clearInterval(intervalId);
resolve();
}
}
//check immediately
check();
//then check every second afterwards
intervalId = setInterval(check, 1000);
});
};
const cancelJob = async() => {
let cancelDownload = true;
const delay = delayWhile(() => cancelDownload);
for(let i = 6; i>0; i--){
console.log('inside for ',i);
setTimeout(()=>{
cancelDownload = false
},4000)
await delay()
console.log('aaaaaaaa');
console.log(`the number is ${i}`);
}
}
cancelJob()
The function you pass to the promise constructor (the promise executor function) is called with two arguments: a function to use to resolve the promise and a function to reject it (conventionally called resolve and reject). Your code should call one of those functions when your asynchronous work is done.
Yours isn't, so the promise never settles, and your code waits forever at the await.
But there are other issues:
1. If you call delay again, it creates a new promise. Your code using await only has access to the first promise, not the ones created by those recursive calls. There isn't really any reason to use recursion here at all.
2. All calls to the function share the same flag. So if we fix the issue with not fulfilling the promise, the loop does wait, but only once:
let cancelDownload = false;
const delay = () => {
return new Promise((resolve) => {
tick();
function tick() {
// Cancelled?
if (cancelDownload) {
// Yes, fulfill the promise
console.log("Flag is set, fulfilling");
resolve(null);
} else {
// No, wait another second
console.log("waiting 1000 and checking again");
setTimeout(tick, 1000);
}
}
});
};
const cancelJob = async () => {
setTimeout(() => {
console.log("Setting cancelDownload to true");
cancelDownload = true;
}, 4000);
for (let i = 6; i > 0; i--) {
console.log("inside for ", i);
await delay();
console.log("aaaaaaaa");
console.log(`the number is ${i}`);
}
};
cancelJob();
.as-console-wrapper {
max-height: 100% !important;
}
I may be mistaken that that second one is a problem for your use case, though, since in a comment on a now-deleted answer you said you wanted the loop only to wait once (the "whole loop" rather than just one iteration).
If you want a function that polls a flag (I don't recommend it, polling is generally not best practice, though sometimes you can't avoid it) and fulfills a promise when it's set, you could use AbortController:
const delay = (signal) => {
let done = false;
return new Promise((resolve) => {
tick();
function tick() {
// Cancelled?
if (signal.aborted) {
// Yes, fulfill the promise with null
console.log("Fulfilling with null");
resolve(null);
} else {
// No, wait another second
console.log("Waiting 1000 and checking again");
setTimeout(tick, 1000);
}
}
});
};
const cancelJob = async () => {
const controller = new AbortController();
setTimeout(() => {
console.log("Cancelling");
controller.abort();
}, 4000);
for (let i = 6; i > 0; i--) {
console.log("inside for ", i);
await delay(controller.signal);
console.log("aaaaaaaa");
console.log(`the number is ${i}`);
}
};
cancelJob();
.as-console-wrapper {
max-height: 100% !important;
}
That also only delays the loop once, because we're passing the same signal to all of the delay functions. Originally I was creating that inside the loop, like this:
const delay = (signal) => {
let done = false;
return new Promise((resolve) => {
tick();
function tick() {
// Cancelled?
if (signal.aborted) {
// Yes, fulfill the promise with null
console.log("Fulfilling with null");
resolve(null);
} else {
// No, wait another second
console.log("Waiting 1000 and checking again");
setTimeout(tick, 1000);
}
}
});
};
const cancelJob = async () => {
for (let i = 6; i > 0; i--) {
const controller = new AbortController();
setTimeout(() => {
console.log("Cancelling");
controller.abort();
}, 4000);
console.log("inside for ", i);
await delay(controller.signal);
console.log("aaaaaaaa");
console.log(`the number is ${i}`);
}
};
cancelJob();
.as-console-wrapper {
max-height: 100% !important;
}
...but then I saw the "whole loop" comment on the other answer.
Note: Normally when you have an asynchronous process with a cancel feature like that, it rejects on cancel with a cancel-specific rejection reason rather than fulfilling, but you get the idea.
One approach to cancellable promises is to supply the promise creator function with a writable parameter like cancelToken. The promise creator populates this parameter with a callback, which, when invoked, cancels this particular promise. This usually leads to simpler and more linear code.
let pause = () => new Promise(r => setTimeout(r, 100));
async function job(name, steps, cancelToken) {
let cancelled = false;
cancelToken.callback = () => cancelled = true;
for (let step = 1; step <= steps; step++) {
console.log(name, step);
await pause();
if (cancelled) {
console.log('CANCELLED');
break;
}
}
}
async function main() {
let cancelToken = {};
let job1 = job('ok', 5, cancelToken);
await job1;
let job2 = job('bad', 5, cancelToken);
setTimeout(cancelToken.callback, 100);
await job2;
}
main()
As a general hint, it's better to avoid new Promise and callbacks whether possible. Note how in this example Promise and setTimeout are only used for testing purposes (simulate real work) and the rest of the code won't change once we replace pause with something more useful like fetch.
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));
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.