ES6 variable scopes in loops with await inside - javascript

As we may know, var keyword defines a variable globally, or locally to an entire function regardless of block scope. So the below code will log 5 times with the same value.
for(var i = 0; i < 5; i++){
setTimeout(() => console.log(i), 2000);
}
To visualize the above JS runtime like this
As you can see, 5 tasks in Callback Queue will wait until Call stack is empty. So after the synchronous loop is done - It means Call stack is empty in my case, then 5 scheduled tasks - console.log(i) with the value of i is equal to 5 will be executed. You can play around here
And what I want is to log right after i == 2. It works as I expected.
var sleep = (ms) => new Promise(resolve => setTimeout(() => resolve(1), ms));
async function runAsync(){
for(var i = 0; i < 5; i++){
if(i == 2) await sleep(2000);
setTimeout(() => console.log(i));
}
}
runAsync();
But I'm curious how it works while I'm not sure the call stack is empty? Whether when I'm using await, allowing the caller of the async function to resume execution, or some else?
Any explanation/visualization of image flow would be appreciated. Thanks.

await cedes control of the thread and allows other processes to run until the promise being awaited resolves. Even if the promise is already resolved, await will yield to any "microtasks" that have been waiting to execute, but that's a moot point in your case because your promise takes a full two seconds to resolve.
In your case, two setTimeouts are queued up before the await, so they are allowed to run when the await happens.
The timeline is basically like this:
i = 0
setTimeout 1 scheduled
i = 1
setTimeout 2 scheduled
i = 2
await
setTimeout 1 callback runs
setTimeout 2 callback runs
setTimeout 3 scheduled
i = 3
setTimeout 4 scheduled
i = 4
setTimeout 5 scheduled
i = 5
loop ends
setTimeout 3 callback runs
setTimeout 4 callback runs
setTimeout 5 callback runs
You can see that i is 2 when the first pair of setTimeouts are allowed to execute, and it is 5 when the remaining 3 execute.
Here is a snippet that hopefully demonstrates the process a little better:
var sleep = (ms) => new Promise(resolve => setTimeout(() => resolve(1), ms));
async function runAsync() {
for (var i = 0; i < 5; i++) {
console.log('i is now', i);
if (i == 2) {
console.log('about to sleep');
await sleep(5000);
console.log('now done sleeping');
}
console.log('about to setTimeout. i is', i, 'right now');
setTimeout(() => {
console.log('setTimeout task running:', i, '- scheduling a new timeout.');
setTimeout(() => console.log('inner timeout:', i), 1000);
});
}
console.log('done looping. i is', i);
}
runAsync();

Related

How does an async function called with await can block the event loop?

In the following code, the 'Hey, timeout executed!' is only printed after the loop in the longLoop function is finished.
setTimeout(() => console.log('Hey, timeout executed!'), 100);
let longLoop = async () => {
for (i = 0; i < 1000000000; i++) {
}
}
let exec = async () => {
await longLoop();
console.log('Loop finished')
}
exec()
However, when I change the longLoop to have an await inside it, the 'Hey, timeout executed!' will print as soon as the await in the loop is reached, like in the following:
setTimeout(() => console.log('Hey, timeout executed!'), 100);
let longLoop = async () => {
for (i = 0; i < 1000000000; i++) {
await new Promise(r => setTimeout(r, 100));
}
}
let exec = async () => {
await longLoop();
console.log('Loop finished')
}
exec()
Why does in the first example the event loop seems to be blocked even if I call the longLoop from the exec using an await? And why it doesn't get blocked in the second example just by using an await inside the loop?
I'm just starting in JavaScript, so I thought that just by a function being async and being called with an await inside another async function, it would be executed in a way that the event loop would not be blocked. Due to that, I'm having kinda a hard time to understand the flow of execution.
This is a case of microtasks vs (macro)tasks. Promises and async/await enter the microtask queue. setTimeout callbacks enter the (macro)task queue. microtasks get run before (macro)tasks (when the stack is empty to be precise), that's why you see the async/await functions get executed before the setTimeout callback
See also:
Difference between microtask and macrotask within an event loop context
https://stackoverflow.com/a/71195496/4651083

Rearming a promise when used with setInterval and setTimeout [duplicate]

function first(){
console.log('first')
}
function second(){
console.log('second')
}
let interval = async ()=>{
await setInterval(first,2000)
await setInterval(second,2000)
}
interval();
Imagine that I have this code above.
When I run it, first() and second() will be called at the same time; how do I call second() after first)() returns some data, for example, if first() is done, only then call second()?
Because first() in my code will be working with a big amount of data and if this 2 functions will be calling at the same time, it will be hard for the server.
How do I call second() each time when first() will return some data?
As mentioned above setInterval does not play well with promises if you do not stop it. In case you clear the interval you can use it like:
async function waitUntil(condition) {
return await new Promise(resolve => {
const interval = setInterval(() => {
if (condition) {
resolve('foo');
clearInterval(interval);
};
}, 1000);
});
}
Later you can use it like
const bar = waitUntil(someConditionHere)
You have a few problems:
Promises may only ever resolve once, setInterval() is meant to call the callback multiple times, Promises do not support this case well.
Neither setInterval(), nor the more appropriate setTimeout() return Promises, therefore, awaiting on them is pointless in this context.
You're looking for a function that returns a Promise which resolves after some times (using setTimeout(), probably, not setInterval()).
Luckily, creating such a function is rather trivial:
async function delay(ms) {
// return await for better async stack trace support in case of errors.
return await new Promise(resolve => setTimeout(resolve, ms));
}
With this new delay function, you can implement your desired flow:
function first(){
console.log('first')
}
function second(){
console.log('second')
}
let run = async ()=>{
await delay(2000);
first();
await delay(2000)
second();
}
run();
setInterval doesn't play well with promises because it triggers a callback multiple times, while promise resolves once.
It seems that it's setTimeout that fits the case. It should be promisified in order to be used with async..await:
async () => {
await new Promise(resolve => setTimeout(() => resolve(first()), 2000));
await new Promise(resolve => setTimeout(() => resolve(second()), 2000));
}
await expression causes async to pause until a Promise is settled
so you can directly get the promise's result without await
for me, I want to initiate Http request every 1s
let intervalid
async function testFunction() {
intervalid = setInterval(() => {
// I use axios like: axios.get('/user?ID=12345').then
new Promise(function(resolve, reject){
resolve('something')
}).then(res => {
if (condition) {
// do something
} else {
clearInterval(intervalid)
}
})
}, 1000)
}
// you can use this function like
testFunction()
// or stop the setInterval in any place by
clearInterval(intervalid)
You could use an IFFE. This way you could escape the issue of myInterval not accepting Promise as a return type.
There are cases where you need setInterval, because you want to call some function unknown amount of times with some interval in between.
When I faced this problem this turned out to be the most straight-forward solution for me. I hope it help someone :)
For me the use case was that I wanted to send logs to CloudWatch but try not to face the Throttle exception for sending more than 5 logs per second. So I needed to keep my logs and send them as a batch in an interval of 1 second. The solution I'm posting here is what I ended up using.
async function myAsyncFunc(): Promise<string> {
return new Promise<string>((resolve) => {
resolve("hello world");
});
}
function myInterval(): void {
setInterval(() => {
void (async () => {
await myAsyncFunc();
})();
}, 5_000);
}
// then call like so
myInterval();
Looked through all the answers but still didn't find the correct one that would work exactly how the OP is asked. This is what I used for the same purpose:
async function waitInterval(callback, ms) {
return new Promise(resolve => {
let iteration = 0;
const interval = setInterval(async () => {
if (await callback(iteration, interval)) {
resolve();
clearInterval(interval);
}
iteration++;
}, ms);
});
}
function first(i) {
console.log(`first: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
function second(i) {
console.log(`second: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
(async () => {
console.log('start');
await waitInterval(first, 1000);
await waitInterval(second, 1000);
console.log('finish');
})()
In my example, I also put interval iteration count and the timer itself, just in case the caller would need to do something with it. However, it's not necessary
In my case, I needed to iterate through a list of images, pausing in between each, and then a longer pause at the end before re-looping through.
I accomplished this by combining several techniques from above, calling my function recursively and awaiting a timeout.
If at any point another trigger changes my animationPaused:boolean, my recursive function will exit.
const loopThroughImages = async() => {
for (let i=0; i<numberOfImages; i++){
if (animationPaused) {
return;
}
this.updateImage(i);
await timeout(700);
}
await timeout(1000);
loopThroughImages();
}
loopThroughImages();
Async/await do not make the promises synchronous.
To my knowledge, it's just a different syntax for return Promise and .then().
Here i rewrote the async function and left both versions, so you can see what it really does and compare.
It's in fact a cascade of Promises.
// by the way no need for async there. the callback does not return a promise, so no need for await.
function waitInterval(callback, ms) {
return new Promise(resolve => {
let iteration = 0;
const interval = setInterval(async () => {
if (callback(iteration, interval)) {
resolve();
clearInterval(interval);
}
iteration++;
}, ms);
});
}
function first(i) {
console.log(`first: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
function second(i) {
console.log(`second: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
// async function with async/await, this code ...
(async () => {
console.log('start');
await waitInterval(first, 1000);
await waitInterval(second, 1000);
console.log('finish');
})() //... returns a pending Promise and ...
console.log('i do not wait');
// ... is kinda identical to this code.
// still asynchronous but return Promise statements with then cascade.
(() => {
console.log('start again');
return waitInterval(first, 1000).then(() => {
return waitInterval(second, 1000).then(() => {
console.log('finish again');
});
});
})(); // returns a pending Promise...
console.log('i do not wait either');
You can see the two async functions both execute at the same time.
So using promises around intervals here is not very useful, as it's still just intervals, and promises changes nothing, and make things confusing...
As the code is calling callbacks repeatedly into an interval, this is, i think, a cleaner way:
function first(i) {
console.log(`first: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
function second(i) {
console.log(`second: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
function executeThroughTime(...callbacks){
console.log('start');
let callbackIndex = 0; // to track current callback.
let timerIndex = 0; // index given to callbacks
let interval = setInterval(() =>{
if (callbacks[callbackIndex](timerIndex++)){ // callback return true when it finishes.
timerIndex = 0; // resets for next callback
if (++callbackIndex>=callbacks.length){ // if no next callback finish.
clearInterval(interval);
console.log('finish');
}
}
},1000)
}
executeThroughTime(first,second);
console.log('and i still do not wait ;)');
Also, this solution execute a callback every secondes.
if the callbacks are async requests that takes more than one sec to resolve, and i can't afford for them to overlap, then, instead of doing iterative call with repetitive interval, i would get the request resolution to call the next request (through a timer if i don't want to harass the server).
Here the "recursive" task is called lTask, does pretty much the same as before, except that, as i do not have an interval anymore, i need a new timer each iteration.
// slow internet request simulation. with a Promise, could be a callback.
function simulateAsync1(i) {
console.log(`first pending: ${i}`);
return new Promise((resolve) =>{
setTimeout(() => resolve('got that first big data'), Math.floor(Math.random()*1000)+ 1000);//simulate request that last between 1 and 2 sec.
}).then((result) =>{
console.log(`first solved: ${i} ->`, result);
return i==2;
});
}
// slow internet request simulation. with a Promise, could be a callback.
function simulateAsync2(i) {
console.log(`second pending: ${i}`);
return new Promise((resolve) =>{
setTimeout(() => resolve('got that second big data'), Math.floor(Math.random()*1000) + 1000);//simulate request that last between 1 and 2 sec.
}).then((result) =>{ // promise is resolved
console.log(`second solved: ${i} ->`,result);
return i==4; // return a promise
});
}
function executeThroughTime(...asyncCallbacks){
console.log('start');
let callbackIndex = 0;
let timerIndex = 0;
let lPreviousTime = Date.now();
let lTask = () => { // timeout callback.
asyncCallbacks[callbackIndex](timerIndex++).then((result) => { // the setTimeout for the next task is set when the promise is solved.
console.log('result',result)
if (result) { // current callback is done.
timerIndex = 0;
if (++callbackIndex>=asyncCallbacks.length){//are all callbacks done ?
console.log('finish');
return;// its over
}
}
console.log('time elapsed since previous call',Date.now() - lPreviousTime);
lPreviousTime = Date.now();
//console.log('"wait" 1 sec (but not realy)');
setTimeout(lTask,1000);//redo task after 1 sec.
//console.log('i do not wait');
});
}
lTask();// no need to set a timer for first call.
}
executeThroughTime(simulateAsync1,simulateAsync2);
console.log('i do not wait');
Next step would be to empty a fifo with the interval, and fill it with web request promises...

How to run multiple timer functions, one after the other in vanilla javascript

Lets say I want to run multiple timer functions, one after the other i.e firstly one function runs for 5 mins then after the completion of the first countdown, another timer begins to run for another 2 mins.
I implemented the timer function as below
function timer(count) {
console.log(count)
let counter = setInterval(() => {
count = count - 1;
if (count < 0) {
clearInterval(counter);
return;
}
console.log(count)
}, 1000);
}
Then when I call this function twice with different arguments as
timer(15);
timer(5);
I get the output as
15
5
14
4
13
3
11
1
10
0
9
8
.
.
0
However my desired output is
15
14
.
.
2
1
0
5
4
3
2
1
0
The problem is that your timer function immediately starts a timer. Timers are asynchronous, so when you call it twice, you just start two timers immediately and they run in parallel.
If you want something to happen after one is finished, then you have to explicitly say so. You have two options:
Callback
This is slightly "older" style of dealing with async code. You call a function that will do something later and then give it a function as a parameter for what to do after it's done:
function timer(count, callback = () => {}) { //<-- take callback
//if callback is not supplied, it's going to be an empty function
console.log(count)
let counter = setInterval(() => {
count = count - 1;
if (count < 0) {
clearInterval(counter);
callback(); //<-- run callback after this timer is finished
return;
}
console.log(count)
}, 1000);
}
//run a timer for 15 then a timer for 5
timer(15, () => timer(5));
This works but excessive use of callbacks can lead to what's known as callback hell. The example here is prone to it as well, for example if you want to run a timer for 5, then for 4, then for 3, then for 2, then for 1, you end up with this:
timer(5,
() => timer(4,
() => timer(3,
() => timer(2,
() => timer(1)
)
)
)
);
Or in one line (for fun):
timer(5, () => timer(4, () => timer(3, () => timer(2, () => timer(1)))));
Promise
Promises are a newer way of handling async operations and help keep the code cleaner.
function timer(count) {
console.log(count)
return new Promise(resolve => { //return a Promise
let counter = setInterval(() => {
count = count - 1;
if (count < 0) {
clearInterval(counter);
resolve(); //it is resolved when the count finishes
return;
}
console.log(count)
}, 1000);
});
}
//run a timer for 15 then a timer for 5
timer(15)
.then(() => timer(5));
One thing Promises help out is the callback hell, now if you want to run a timer for 5, then for 4, then for 3, then for 2, then for 1, you get a much more reasonable code:
timer(5)
.then(() => timer(4))
.then(() => timer(3))
.then(() => timer(2))
.then(() => timer(1));
No more nesting.
async/await
This is actually Promises again. But wearing a disguise. If a function returns a Promise, you can await it which waits until the Promise is resolved, then executes the next lines of code. You can only use await inside an async function, however since await actually transforms your code to a Promise behind the scenes. Functionally, there is little difference but you can just structure your code differently:
function timer(count) {
console.log(count)
return new Promise(resolve => { //return a Promise
let counter = setInterval(() => {
count = count - 1;
if (count < 0) {
clearInterval(counter);
resolve(); //it is resolved when the count finishes
return;
}
console.log(count)
}, 1000);
});
}
//run a timer for 15 then a timer for 5
async function main() { //you can only use `await` in async funcions
await timer(15);
await timer(5);
}
/* the above will actually be transformed behind the scenes into:
timer(15)
.then(() => timer(5));
*/
main();
You have to wait for the first call to finish before doing the second call. An easy way to do that is by wrapping the setInterval call with a Promise and call resolve when the counter reaches 0.
Using Promise (ES6 but runs on most browsers, needs Polyfill for IE)
timer(15).then(timer.bind(0, 5));
function timer(count) {
console.log(count);
return new Promise(function(resolve) {
var intervalID = setInterval(function() {
count = count - 1;
if (count < 0) {
clearInterval(intervalID);
resolve();
return;
}
console.log(count);
}, 1000);
});
}
Using async and await (ES7)
(async function() {
await timer(15);
await timer(5);
})();
async function timer(count) {
console.log(count);
return new Promise(resolve => {
let intervalID = setInterval(() => {
count = count - 1;
if (count < 0) return resolve(), clearInterval(intervalID);
console.log(count);
}, 1000);
});
}
The best way to work with async stuff in modern javascript are promises and async/await. But because there is no default way to wait we need to write it ourself. Thats pretty simple tho:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
Now we can use this to write your timer with async/await and make it return a Promise:
async function timer(count) {
for(let i = count; i > 0; i--) {
await wait(1000);
console.log(i);
}
}
And not its easy to use timer multiple times in an async function:
await timer(15);
await timer(5);
So the full code is:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
async function timer(count) {
for(let i = count; i > 0; i--) {
await wait(1000);
console.log(i);
}
}
async function multipleTimers() {
await timer(15);
await timer(5);
}
multipleTimers();
It appears you wanted the second timer function to be executed only when all the intervals from the first one are completed. This is absolutely not what you implemented. As it is, the first function is called, which sets the interval, and finishes. The main code is not interrupted by the setting of the interval, so it will simply keep executing, calling the second timer function.
According with your desired output, you will certainly need an async function, to know when the first interval is done executing. Here is what you can try:
function timer(count) {
return new Promise(resolve => {
console.log(count)
let counter = setInterval(() => {
count = count - 1;
if (count < 0) {
clearInterval(counter);
resolve();
return;
}
console.log(count)
}, 1000);
}
}
timer(15).then(() => {
timer(5);
});
If you are already in an async function, the last bit can also be written as:
await timer(15);
timer(5);
You should give a read about async functions. It's really worth it.

Promise method to wait 5 seconds once print out value

Currently creating a simple program to count back from 25 to 0. Each time it prints out the current value, the program should wait 5 seconds using a Promise(.then()) syntax. I'm a JS novice and just can't figure out how to do it in this specific way. Thanks so much.
count = 25;
while (count >= 0) {
console.log(count).then(() => wait(7000));
count = count - 1;
}
Well, you can't just attach .then to anything; that's just a method that you can chain onto Promises. console.log does not return a Promise, so you can't chain .then directly onto it. And Promises run asynchronously, meaning once you trigger one, it will do its thing while the code continues on; that's the point of Promises. So you can't put one inside a while loop and expect it to wait before continuing.
If you want to do this with Promises, instead you can use either async/await or construct an infinite Promise chain. I'd suggest the former.
So you'd first need to create your wait function, which should return a Promise that resolves after the specified amount of time:
function wait(ms) {
return new Promise((resolve, reject) => setTimeout(resolve, ms));
}
Then you use an async function so you can make it await the Promises in each loop, though I'd suggest using a for loop instead of while since you have a set count of iterations:
async function DoLoop() {
for (let count = 25; count >= 0; --count) {
console.log(count);
await wait(7000);
}
}
Keep in mind, again, that async functions and Promises are, by definition, asynchronous, which means if you call the now-async DoLoop function somewhere, your code will continue running past that call while the loop is also running at the same time -- the code that called DoLoop won't wait for it to finish.
Here is a version of the answer of IceMetalPunk without async/await.
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
let chain = Promise.resolve();
for (let count = 25; count >= 0; --count) {
chain = chain.then(() => {
console.log(count);
return wait(7000);
});
}
Try this, no need for promises
Proof: https://jsfiddle.net/9f7rz2ck/2/
Without promise use setInterval to avoid looping through setTimeouts
let counter = 25;
let timer = setInterval(() => {
--counter;
console.log('Printing after 5 seconds', counter);
if (counter === 0) {
clearInterval(timer)
}
}, 5 * 1000)
With promise, when a promise resolves it's over, you have to have 25 promises, therefore the loop.
let counter = 25;
function timeOut(i) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(--counter);
}, 5 * 1000 * i)
});
}
for (let i = 1; i <= 25; i++) {
timeOut(i).then((x) => console.log('counter:', x))
}

How to write async code in JS?

JavaScript is single thread language, which means all user-written code will run in the main thread. For example, in Node.js, the async IO read is an async operation, it runs in a worker thread, but the callback which developer has written run in the main thread as other JS code. So if I identify one JS function with async, it actually did not run in other thread, more important, an async also doesn't mean non-blocking.
const sleep = (wait) => {
const start = new Date().getTime();
while (new Date().getTime() - start <= wait) {}
}
const async_print1 = async () => {
setTimeout(() => {
sleep(2000);
console.log('async_print1'); /// in 4s.
}, 2000);
}
const async_print2 = async () => {
setTimeout(() => {
sleep(1000);
console.log('async_print2'); /// in 5s.
}, 2000);
}
const sync_print = () => {
sleep(1000);
console.log('sync_print'); /// in 1s.
}
async_print1();
async_print2();
sync_print();
The output:
[00:00] <program start>
[00:01] sync_print
[00:04] async_print1
[00:05] async_print2
[00:05] <over>
Fisrt, sync_print run in main thread, it sleep 1s then print. Then, two timer start, after 2s, run loop need call two callback, the two callback both run in main thread, so they're blocking operations.
My question is how to make two sleep() run in other thread? Or just can not?
**Updated my question **
Sorry for my poor english and expression, I finally understand. Thanks you. Is it possible to execute Javascript functions with multi-threads
There is no way to turn synchronous code into asynchronous code. If the event loop is busy running your while loop (which is blocking code), then it is going to be too busy to do anything else. The async keyword just makes a function return a promise (and allows you to use await inside it).
You can shunt code off into another thread using Web Workers.
You probably don't need web workers yet. It looks like you forgot await altogether -
const sleep = ms =>
new Promise (r => setTimeout (r, ms))
const asyncPrint1 = async () =>
{ await sleep (2000)
console.log ("async print 1")
}
const asyncPrint2 = async () =>
{ await sleep (2000)
console.log ("async print 2")
}
const syncPrint = () =>
{ console.log ("sync print")
}
const main = async () =>
{ await asyncPrint1 () // ...2 seconds
await asyncPrint2 () // ...4 seconds
await sleep (1000) // ...5 seconds
syncPrint ()
}
main ()
.then (console.log, console.error)
// async print 1
// async print 2
// sync print
Inside an async function, you can await as many other async calls as you want -
const sleep = ms =>
new Promise (r => setTimeout (r, ms))
const main = async () =>
{ console.log ("begin")
await sleep (1000)
console.log ("1 second has passed")
await sleep (1000)
await sleep (1000)
console.log ("3 seconds have passed")
await sleep (1000)
await sleep (1000)
await sleep (1000)
console.log ("6 seconds have passed")
}
main ()
.then (console.log, console.error)
// begin
// 1 second has passed
// 3 seconds have passed
// 6 seconds have passed
// undefined

Categories