nodejs async loop with delay - javascript

I'm trying to get a loop working with a longer delay, when the task takes longer as planned.
currently I'm using this code for the loop:
async function doSomeStuff() {
// do some stuff
// sometimes this action may take longer than 5 seconds
// after finishing wait 5 seconds
console.log('task completed, waiting 5 seconds for next run...');
}
setInterval(doSomeStuff, 5000);
works like a charm, but the delay is fixed at 5 seconds, even if the tasks takes longer as planned, so that sometimes the new loop starts only 1 second after finishing the last one instead of waiting 5 seconds.
unfortunately, I was not able to solve it myself by looking at the other questions.
I am grateful for any help.
Best regards
Paedda

async functions shouldn't be used with API that ignores a promise that it returns such as setInterval, in case it's expected that a promise should be chained.
This can be done with recursive async function:
(async function doSomeStuff() {
await new Promise(resolve => setTimeout(resolve, 5000));
// do some stuff
await doSomeStuff();
})();
Or infinite loop:
(async function doSomeStuff() {
while (true) {
await new Promise(resolve => setTimeout(resolve, 5000));
// do some stuff
}
})();
Function body can be wrapped with try..catch if needed to handle errors.

You were almost there
async function doSomeStuff() {
// do some stuff
// sometimes this action may take longer than 5 seconds
// after finishing wait 5 seconds
console.log('task completed, waiting 5 seconds for next run...');
setTimeout(doSomeStuff, 5000);
}

Related

Asynchronous Javascript: Run two functions at the same time

I am trying to improve my understanding of asynchronous JavaScript. To do this I have made a function that can take a long time to complete, which would completely block any other code from executing.
To test this, the function counts up. the first call counts from 1 to 10000000. The second call counts from 10 to 100.
CODE
async function LongTask(from, to) {
//Count up
let myNum = from
console.log(`counting from ${myNum} to ${to}`)
let start = new Date();
while (myNum != to) {
//console.log(`myNum: ${myNum}`);
await myNum++;
}
let end = new Date();
console.log(`counting from ${from} to ${to} done in ${end - start}ms!`);
}
//2 functions that take some time to finish
LongTask(1, 10000000);
LongTask(10, 100);
//Do unrelated stuff
console.log("Hello World!");
OUTPUT
counting from 1 to 10000000
counting from 10 to 100
Hello World!
counting from 10 to 100 done in 2ms!
counting from 1 to 10000000 done in 40389ms!
I managed to get it so that 10 to 100 will finish first due to it being faster. However, I get a warning on line 10 await myNum++; saying 'await' has no effect on the type of this expression. Removing the await keyword results in code blocking making it so that the 10 to 100 call of the function will have to wait for the much longer, unrelated 1 to 10000000 call to finish before being called.
Am I misunderstanding how asynchronous JavaScript works and is there a better way to ensure the 10 to 100 call finishes first?
You can only await a Promise which your function (and every async function) returns. You'll have to await both promises at some point in order to make sure the program does not exit before the promises resolve.
You can use Promise.all() to await multiple promises. I altered your example to await a predefined time span to illustrate how this works, as I then exactly know when I expect a function to finish.
(async() => {
async function LongTask(ms) {
console.log(`starting task that will take ${ms} milliseconds`)
await waitFor(ms);
console.log(`ended task that took ${ms} milliseconds`);
}
async function waitFor(ms) {
return new Promise((resolve, reject) => setTimeout(() => resolve(), ms))
};
//2 functions that take some time to finish
const start = new Date();
console.log(`Calling both functions at ${start}`)
const countToSmallNumberPromise = LongTask(4000);
const countToLargeNumberPromise = LongTask(1000);
//Do unrelated stuff
console.log("Hello World!");
// await both promises to make sure they finish before the program finishes
await Promise.all([countToLargeNumberPromise, countToSmallNumberPromise])
const end = new Date();
// program will end after approx. 4 seconds (at least 4 seconds), so both functions actually run in parallel
// (that's actually not correct, there is no real parallelism here, as Node or your browser are single-threaded)
// To be correct: they run concurrently, not in parallel.
console.log(`Ending program at ${end}. Program ran for ${end - start} milliseconds`);
})();
FYI: Difference between concurrent and in parallel

How to "queue" requests to run all the time without setInterval?

I am reading data in realtime from a device through http requests, however the way I am currently doing it is, something like this:
setInterval(() => {
for (let request of requests) {
await request.GetData();
}
}, 1000);
However, sometimes there is lag in the network, and since there are 4-5 requests, sometimes they don't all finish within a second, so they start stacking up, until the device eventually starts to timeout, so I need to somehow get rid of the setInterval. Increasing the time is not an option.
Essentially, I want them to get stuck in an infinite loop and I can add an inner timer to let the requests run again when half a second or a second has passed since the last run, but how do I get them stuck in an infinite loop without blocking the rest of the application?
Or maybe a way to make setInterval wait for all requests to finish before starting to count the 1 second interval?
Try:
(async () => {
while (true) {
for (let request of requests) {
await request.GetData();
}
await new Promise((resolve) => setTimeout(resolve, 1000));
}
})();
This will only start waiting once all the requests are finished, preventing them from stacking up.
Alternatively, on the inside of the async function, use:
while (true) {
await Promise.all(requests.map(request => request.GetData()));
await new Promise((resolve) => setTimeout(resolve, 1000));
}
This is different because all the calls to request.GetData() will run concurrently, which may or may not be what you want.

Invoking synchronous function in asynchronous function

I have problem understanding how async function work in JavaScript. Let's take the code below:
async () => {
console.log(1);
setTimeout(() => console.log(2)
, 2000)
console.log(3);
}
I would expect that invocation of synchronous function inside async should block thread before executing further code. So I'd expect to get 1 -> 2 -> 3 instead I'm getting 1 -> 3 -> 2. Can't find explanation why it's happening and how to block thread to receive output 1 -> 2 -> 3.
I'm using Node 12 with serverless framework.
async itself won't wait until execution of setTimeout finished as you expect. As you can read from the documentation - find here for async:
An async function can contain an await expression that pauses the execution of the async function to wait for the passed Promise's resolution, then resumes the async function's execution and evaluates as the resolved value.
Just built a quick example to see the difference between async and await solution and just using setTimeout as it is in your example.
Consider the following example:
const getPromise = async () => {
return new Promise((resolve) => {
setTimeout(resolve, 3000);
});
}
const run = async () => {
console.log('run started');
setTimeout(() => console.log('setTimeout finised'), 2000);
console.log('setTimeout started');
const promise = await getPromise();
console.log('Promise resolved');
console.log('run finished');
}
run();
Steps explained:
Logging that function run execution started
Attaching an event to setTimeout which will be finished in ~2 seconds
Log into console that setTimeout started
Creating promise with getPromise function and with await keyword wait till resolve
In ~2 seconds setTimeout logs that it has been finished
In ~3 seconds from Promise the resolve function called
run function finishes its execution after resolve and logging.
I hope that helps you understand this portion of the code.
I would expect that invocation of synchronous function inside async should block thread before executing further code.
It does
So I'd expect to get 1 -> 2 -> 3 instead I'm getting 1 -> 3 -> 2.
setTimeout is not synchronous. It very explicitly is for queuing up a function to run later, after some time has passed.
Can't find explanation why it's happening and how to block thread to receive output 1 -> 2 -> 3.
You can't per se.
The closest you could come would be to replace setTimeout with a loop that runs around in circles until some time has passed… but that would be awful (it would lock up the UI for starters).
If you want to just run the three logs in order, then replace setTimeout with something that returns a promise and then await it (which would put the async function to sleep and let any other code continue to run until the promise resolved).
const timeout = function() {
return new Promise(function(res) {
setTimeout(() => {
console.log(2);
res();
}, 2000)
})
};
const x = async() => {
console.log(1);
await timeout();
console.log(3);
}
x();

How to manually pause and execute for batch of records using map and promises in javascript?

function sleep(ms) {
var start = new Date().getTime(),
expire = start + ms;
while (new Date().getTime() < expire) {}
return;
}
async function executeWithDelay(offers) {
return Promise.all(
offers.map((offer, i) =>
getDetailedInfo(offer).then(data => {
offer = data;
if (i % 5 === 0) {
console.log('executed but it delays now for 3 seconds');
sleep(3000);
}
})
)
).then(function(data) {
return offers;
});
}
Trying to achieve web-scraping with possible best solution available. I am combining cheerio and puppeteer together and i have some nice code to debug. The above code works fine if the offers data is less i.e 5-10 records. If it exceeds, the browser gets crashed, though i am running in headless version.
This is basically due to in-house bug from the package what i am using which is unable to handle the load. My experiment is to a little delay for every batch of records and complete the full records execution.
But with the below code, it's behaving as non-blocking code and first time it delays and rest is executed constantly.
Should i use for loop instead of map or is there any alternative to handle this situation?
You never want to "sleep" with a busy-wait loop like that. It means nothing else can be done on the main thread.
Once the operations the promises represent have been started, unless they provide some custom means of pausing them, you can't pause them. Remember, promises don't actually do anything; they provide a standard way to observe something being done.
Another problem with that code is that you're returning offers, not the results of calling getExtendedInfo.
If the problem is calling getExtendedInfo too often, you can insert a delay (using setTimeout, not a busy-loop). For instance, this does the first request to getExtendedInfo almost immediately, the next after one second, the next after two seconds, and so on:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function executeWithDelay(offers) {
return Promise.all(
offers.map((offer, i) =>
sleep(i * 1000).then(getDetailedInfo)
)
});
}
Obviously, you can adjust that delay as required.
(Note that executeWithDelay isn't declared async; no need, since it has to use Promise.all anyway.)

About random result of a snippet of event loop code

I'm studying about javascript event loop, and I tried some complex and nested async codes, and one of them complicated me so much. The code snippet looks like:
console.log(1);
new Promise((resolve, reject) => {
console.log(2);
resolve();
}).then(() => {
setTimeout(() => { console.log(3) }, 0);
});
setTimeout(() => {
new Promise((resolve, reject) => {
console.log(4);
resolve();
}).then(() => { console.log(5) });
});
And the result sometimes is 1 - 2 - 4 - 5 - 3, and sometimes is 1 - 2 - 4 - 3- 5.
It performs the same in browser environment and node environment.
Maybe my code is written wrong, or there are some issues exist in V8 resolving event loop?
Callback execution order is not really deterministic. Whenever a Promise resolves, a task that executes the .then() chain gets pushed onto the Eventqueue. The same happens when a timer resolves. As timers run concurrently, they might not finish exactly in the order they were started, if they are close to each other.
main code executes (1) {
promise (2) gets pushed onto the queue
promise (4) gets pushed onto the queue
}
promise resolves (2) {
a 0ms timer gets set (3)
}
// maybe the timer is done already (3)
promise resolves(4)
// maybe the timer is done now (3)
You are combining two things: Promise.resolve and window.setTimeout. The former runs synchronously and places the resolved item in the queue immediately. The window.setTimeout however has a different approach. It start to handle the provided function once the timer has expired. When using 0 as delay in window.setTimeout as you have used in your first promise:
setTimeout(() => { console.log(3) }, 0);
then it does not mean "run this immediately". It is more "run this function as soon as possible". That can be changed because the timer is being throttled by browsers. If you do not have problems to read some specs, you can read timer-initialisation-steps to comprehend how it is being initialized in detail.
There is a more easier-to-read information on MDN: Reasons for delays longer than specified
Dependent of the browser/engine, the engine might be busy with (background) tasks so that timers gets throttled. As you have experienced yourself, there is a situation where you are getting different results. The minimum throttling time is (according to the specs, but browsers can use a different value) 4ms. The end result is that the function in the throttled timer gets executed after the another timer that did not got throttled.

Categories