Why is async function blocking? - javascript

Here is the code I'm running:
async function sleep(ms) {
const start = new Date();
while (new Date() - start < ms) { };
}
const start = new Date();
sleep(5000).then(() => console.log("1!"));
console.log(new Date() - start, "ms");
sleep(5000).then(() => console.log("2!"));
console.log(new Date() - start, "ms");
The output I would expect is this:
1 ms (or some other small number of ms)
2 ms (or some other small number of ms)
1!
2!
Instead what I see is this:
5000 ms
10005 ms
1!
2!
I'm a bit confused by this. Firstly, why are the two sleep functions not running asynchronously? Why doesn't the second call to sleep start until the first one is finished?
Secondly, if the second call doesn't start until the first is completed, why is 1! printed before 2!?
If I change the sleep(ms) function to the following, which was suggested in another StackOverflow question, the code works as expected:
function sleep(ms) {
return new Promise(res => setTimeout(res, ms));
}
If I replace sleep(ms) with some slow computation without using a timeout, what should I expect?

JavaScript isn't really multithreaded. It relies on two things to give that illusion:
Computers are really really fast and generally complete whatever needs to be done instantaneously as far as a human can tell
Because they are fast and the world is slow, computers spend a lot of time waiting for something to happen, and can profitably do something else in the meantime
Basically, JavaScript runs a thread until it either finishes, or is waiting for something to happen (e.g. an async network call), at which point JavaScript will switch to another thread, if there is one.
Your first sleep() function is a busy wait. It consumes 100% CPU doing nothing. More importantly, it never gives another thread a chance to run. The promise solution does it the JavaScript way: it ends almost immediately (thereby giving other threads a chance to run) and uses SetTimeout to regain control (in an async sort of way) when other threads aren't running.

The reason why the functions marked as async are not run concurrently with other code is that async does not cause a function to be asynchronous (!). Instead, this keyword marks a function as returning a Promise, and also enables some syntax sugar such as await.
This means that async has absolutely nothing to do with Threads, Tasks, or any other concurrency primitives known from other languages. In a JavaScript program, all visible code is executed in a single thread in a blocking fashion, and it runs to completion. This is why the long while loop blocks program flow.
Why does setTimeout work as expected? Because the code that actually runs is setTimeout and the res callback, nothing more. In the background, the event loop ensures that res is called eventually, but the machinery that handles this is hidden from the developer.
Let's look in depth at the first case.
What happens, step by step, is this - note that user code runs to completion in an uninterrupted fashion:
The first sleep() runs to completion, and its return value's .then() method is called, which enqueues a callback as a so-called microtask (which will log 1!) for the event loop to execute soon
The first console.log runs
The second sleep() runs to completion, and its return value's .then() method is called, which in turn enqueues another microtask
The second console.log runs
<The user program is now finished, but there is stuff in the event loop for the JS engine to process!>
The event loop processes the first microtask: () => console.log("1!")
The event loop processes the second microtask: () => console.log("2!")
<The event loop is now empty - execution is ended>
If you replace the body of the sleep() function with setTimeout, you'll notice that setTimeout itself is quick to execute - all it does is enqueue something for the event loop to process later and return. This is why the flow of execution is not blocked.

Related

SetInterval / Async Await oddity

Consider the two functions below, both async, one a brutal workload that takes a lot of time, and the other one a wait function that waits a precise number of seconds by setting a timeout.
async function Brutal_Workload()
{
for(var x = 0; x< 1000 * 1000 * 1000; x++)
{
}
}
async function Time_Wait(seconds)
{
var promise = new Promise((resolve, reject) =>
{
var msecs = Math.floor(seconds * 1000);
var timer =
setTimeout(
function()
{
clearTimeout(timer);
resolve();
},
msecs);
});
return promise;
}
Now, let's call the first function in a setInterval cycle
setInterval(
async function()
{
await Brutal_Workload();
console.log("BLIP");
}, 1000 / 30);
All as intended: despite the interval running at 30 calls per second, I only get 1 blip per second, because Brutal_Workload is choking it.
But when I use the other function...
setInterval(
async function()
{
await Time_Wait(1);
console.log("BLIP");
}, 1000 / 30);
I get 30 BLIPs per second.
The Time_Wait function, which works otherwise just fine outside of setInterval, doesn't seem to work here.
Any idea of what might cause this behavior?
Ok, rather than continue the back-and-forth in the comments, I'm just going to post this as an answer.
Javascript is both single-threaded and concurrent. I know you know this, but you don't seem to realize the implications. In your first function, you only see a console.log every so often, because your "brutal workload" blocks the only thread of execution until it completes, which means that regardless of what number you passed to setInterval not only is no other invocation running, the next bit of work isn't even being queued to run because your brutal workload is blocking the only thread of execution.
Understand, the runtime environment's setInterval runs on the same (only) thread as your code, the JS engine doesn't cheat and run your stuff in one thread and setInterval in another. So while brutal workload is doing its thing, setInterval itself, much less the function you passed to it, is not running at all. Using async and wrapping your brutal workload in a Promise makes essentially zero difference in terms of our discussion here, because brutal workload dominates.
So that explains the first example, so far so good. On to the second.
Unlike the first example, in the second there is no long-running chunk of code to tie up the thread of execution. So your callback to setInterval runs, dutifully registers a thing to run in a second, and yields control of the thread of execution, something that again the first example does not (and cannot do). Here the Promise and async/await actually does enable concurrency which it can't do in the first example because brutal workload is hogging the thread. So in a fraction of a second your callback to setInterval runs again, dutifully queues up another thing to run after a second has passed, and so on.
So after ~1 second that first queued up log happens, and then after a fraction of a second after that the second, and then so on. This doesn't happen in the first example because although you told setInterval to run 30x/sec brutal workload means that setInterval itself can't run to even queue your callback to be ran.

javsacript / node.js await one more task makes it unblock?

I use while(true) to simulate the js thread blocking as below
setInterval(() => {
console.log(new Date(), 'interval');
}, 500);
while (true) {
console.log(new Date(), 'while true');
}
In the above code, we cannot see interval printed on the terminal because the call stack busy at executing what's in the while(true).
But I can see interval in the below code by adding one more await in the while(true).
main();
async function main() {
setInterval(() => {
console.log(new Date(), 'interval');
}, 500);
while (true) {
console.log(new Date(), 'while true');
await wait();
}
function wait() {
return new Promise(ok => setTimeout(ok, 0));
}
}
Why adding one await in the while(true) would make js main thread have spare time to execute the interval ?
I usually use this image while thinking of async behaviour in js. I'm thinking the call stack is full of what's in the while(true) no matter we put a await or not.
event loop image from
https://medium.com/#swarajgandhi/what-the-heck-is-the-event-loop-anyway-fc5a687a9577
javsacript / node.js await one more task makes it unblock?
It depends upon what the task is that you're awaiting.
Why adding one await in the while(true) would make js main thread have spare time to execute the interval ?
Here's a simplified sequence of steps that happens with this loop:
while (true) {
console.log(new Date(), 'while true');
await wait();
}
Log the date
call wait()
When executing wait() call setTimeout(ok, 0)
Return unresolved promise from wait().
await that unresolved promise.
Your while loop is now suspended until that promise resolves.
Control goes back toward the event loop upon the function suspension at the await.
The first thing that is checked is a few higher priority things like the promise job queue. Nope, nothing there yet.
OK, go back to the main event loop. This main event loop has multiple steps in its cycleand one of those steps is for timers.
When the event loop gets to the timer stage, see what the oldest timer-related event is and call its callback. Hmmm, if there was already an overdue setInterval() timer waiting to run, it will get to run. After it runs, check for any other pending timers and run them.
If there's no overdue setInterval() waiting to run, then the setTimeout(ok, 0) should now be ready the run. The event loop calls its callback which resolves the promise we created earlier.
Now, there is something in the promise job queue so service that. This will resume the main function call on the await where it left off in step #6, and the while loop gets to run another cycle (starting at #1).
Because of step #10, you can see that at some point, there will be an overdue setInterval() waiting to run and it will be in line in front of the most recent setTimeout(ok, 0) so it will get to run. After it runs, the next setTimeout(ok, 0) will get its turn to run.
Thus, you get to see the setInterval() results when you await something that is only resolved when a setTimeout() fires.
So, put even simpler. The setInterval() timer and the setTimeout() timer are served by the same part of the event loop. So, the same code in the event loop that allows your setTimeout() to run which will resolve the promise you are awaiting also allows the setInterval() timer to run. So, when you await the promise that gets resolved by a setTimeout() you're making your code wait until the event loop gets to the part where it serves timers and while it's doing that, it's going to serve any setInterval() firing that was already waiting before you registered your setTimeout(). It won't serve only your setTimeout() without also serving any already waiting setInterval().
So, it's not so much about "spare time" in the event loop, but about ordering in the event loop. A setInterval() that is already overdue will get served by the event loop before your setTimeout(ok, 0). So, the promise in your wait() function won't get resolved until after any setInterval() that was already waiting to run gets to run, thus allowing them to interleave and both get to run.
As a test and a puzzle for further understanding, try changing to this:
function wait() {
return Promise.resolve();
}
This will return an immediately resolved promise which will not let the setInterval() get a chance to run because resolved promises get served before the main event loop that handles timers like setInterval(). So, you never allow the event loop to get around to serving timers as you starve it by constantly giving it a resolved promise to serve.
Note, you will generally not want to program this way (with a loop on an already resolved promise) because you are starving other parts of the event loop. This was just a demo for illustration purposes.

Can I use setTimeout() on JavaScript as a parallel processed function

I know JS is single threaded. But I have a function which takes time for the calculation. I would like it to work paralleled, so this function would not freeze next statement. Calculation inside of function will take approximately 1-2 seconds.
I used to create it using promise, but it still freeze the next statement.
console.log("start");
new Promise((res, rej) => {
/* calculations */
}).then((res) => console.log(res));
console.log("end");
Then I used setTimeout function with time interval 0. LoL
console.log("start");
setTimeout(() => {
/* calculations */
console.log(res);
}, 0);
console.log("end");
Outputs:
start
end
"calculation result"
Both cases shows similar result, but using promise prevented to show console.log("end") before calculation finishes. Using setTimeout works as I wanted, and shows console.log("end") before calculation, so it was not freeze till calculation done.
I hope it was clear enough. Now for me using setTimeout is the best solution, but I would be happy to hear your ideas or any other method calculating concurrently without setTimeout.
The code you write under new Promise(() => {..code here..}) is not asynchronous. The is a very common misconception that everything under the Promise block would run asynchronously.
Instead, this JS API just let's get us a hook of some deferred task to be done once the promise is resolved. MDN
Promises are a comparatively new feature of the JavaScript language that allow you to defer further actions until after a previous action
has completed, or respond to its failure. This is useful for setting
up a sequence of async operations to work correctly.
new Promise(() => {
// whatever I write here is synchromous
// like console.log, function call, setTimeout()/fetch()/async web apis
// if there are some async tasks like fetch, setTimeout.
// they can be async by themselves but their invocation is still sync
//
})
setTimeout is not the correct option either. Code under setTimeout would run when the event stack is empty and once it enters, it would block the main thread again.
The right approach to this would be to use Web Workers.

Difference between returning new Promise and Promise.resolve

For the below code snippet, i would like to understand how NodeJS runtime handles things :
const billion = 1000000000;
function longRunningTask(){
let i = 0;
while (i <= billion) i++;
console.log(`Billion loops done.`);
}
function longRunningTaskProm(){
return new Promise((resolve, reject) => {
let i = 0;
while (i <= billion) i++;
resolve(`Billion loops done : with promise.`);
});
}
function longRunningTaskPromResolve(){
return Promise.resolve().then(v => {
let i = 0;
while (i <= billion) i++;
return `Billion loops done : with promise.resolve`;
})
}
console.log(`*** STARTING ***`);
console.log(`1> Long Running Task`);
longRunningTask();
console.log(`2> Long Running Task that returns promise`);
longRunningTaskProm().then(console.log);
console.log(`3> Long Running Task that returns promise.resolve`);
longRunningTaskPromResolve().then(console.log);
console.log(`*** COMPLETED ***`);
1st approach :
longRunningTask() function will block the main thread, as expected.
2nd approach :
In longRunningTaskProm() wrapping the same code in a Promise, was expecting execution will move away from main thread and run as a micro-task. Doesn't seem so, would like to understand what's happening behind the scenes.
3rd approach :
Third approach longRunningTaskPromResolve() works.
Here's my understanding :
Creation and execution of a Promise is still hooked to the main thread. Only Promise resolved execution is moved as a micro-task.
Am kinda not convinced with whatever resources i found & with my understanding.
All three of these options run the code in the main thread and block the event loop. There is a slight difference in timing for WHEN they start running the while loop code and when they block the event loop which will lead to a difference in when they run versus some of your console messages.
The first and second options block the event loop immediately.
The third option blocks the event loop starting on the next tick - that's when Promise.resolve().then() calls the callback you pass to .then() (on the next tick).
The first option is just pure synchronous code. No surprise that it immediately blocks the event loop until the while loop is done.
In the second option the new Promise executor callback function is also called synchronously so again it blocks the event loop immediately until the while loop is done.
In the third option, it calls:
Promise.resolve().then(yourCallback);
The Promise.resolve() creates an already resolved promise and then calls .then(yourCallback) on that new promise. This schedules yourCallback to run on the next tick of the event loop. Per the promise specification, .then() handlers are always run on a future tick of the event loop, even if the promise is already resolved.
Meanwhile, any other Javascript right after this continues to run and only when that Javascript is done does the interpreter get to the next tick of the event loop and run yourCallback. But, when it does run that callback, it's run in the main thread and therefore blocks until it's done.
Creation and execution of a Promise is still hooked to the main thread. Only Promise resolved execution is moved as a micro-task.
All your code in your example is run in the main thread. A .then() handler is scheduled to run in a future tick of the event loop (still in the main thread). This scheduling uses a micro task queue which allows it to get in front of some other things in the event queue, but it still runs in the main thread and it still runs on a future tick of the event loop.
Also, the phrase "execution of a promise" is a bit of a misnomer. Promises are a notification system and you schedule to run callbacks with them at some point in the future using .then() or .catch() or .finally() on a promise. So, in general, you don't want to think of "executing a promise". Your code executes causing a promise to get created and then you register callbacks on that promise to run in the future based on what happens with that promise. Promises are a specialized event notification system.
Promises help notify you when things complete or help you schedule when things run. They don't move tasks to another thread.
As an illustration, you can insert a setTimeout(fn, 1) right after the third option and see that the timeout is blocked from running until the third option finishes. Here's an example of that. And, I've made the blocking loops all be 1000ms long so you can more easily see. Run this in the browser here or copy into a node.js file and run it there to see how the setTimeout() is blocked from executing on time by the execution time of longRunningTaskPromResolve(). So, longRunningTaskPromResolve() is still blocking. Putting it inside a .then() handler changes when it gets to run, but it is still blocking.
const loopTime = 1000;
let startTime;
function log(...args) {
if (!startTime) {
startTime = Date.now();
}
let delta = (Date.now() - startTime) / 1000;
args.unshift(delta.toFixed(3) + ":");
console.log(...args);
}
function longRunningTask(){
log('longRunningTask() starting');
let start = Date.now();
while (Date.now() - start < loopTime) {}
log('** longRunningTask() done **');
}
function longRunningTaskProm(){
log('longRunningTaskProm() starting');
return new Promise((resolve, reject) => {
let start = Date.now();
while (Date.now() - start < loopTime) {}
log('About to call resolve() in longRunningTaskProm()');
resolve('** longRunningTaskProm().then(handler) called **');
});
}
function longRunningTaskPromResolve(){
log('longRunningTaskPromResolve() starting');
return Promise.resolve().then(v => {
log('Start running .then() handler in longRunningTaskPromResolve()');
let start = Date.now();
while (Date.now() - start < loopTime) {}
log('About to return from .then() in longRunningTaskPromResolve()');
return '** longRunningTaskPromResolve().then(handler) called **';
})
}
log('*** STARTING ***');
longRunningTask();
longRunningTaskProm().then(log);
longRunningTaskPromResolve().then(log);
log('Scheduling 1ms setTimeout')
setTimeout(() => {
log('1ms setTimeout Got to Run');
}, 1);
log('*** First sequence of code completed, returning to event loop ***');
If you run this snippet and look at exactly when each message is output and the timing associated with each message, you can see the exact sequence of when things get to run.
Here's the output when I run it in node.js (line numbers added to help with the explanation below):
1 0.000: *** STARTING ***
2 0.005: longRunningTask() starting
3 1.006: ** longRunningTask() done **
4 1.006: longRunningTaskProm() starting
5 2.007: About to call resolve() in longRunningTaskProm()
6 2.007: longRunningTaskPromResolve() starting
7 2.008: Scheduling 1ms setTimeout
8 2.009: *** First sequence of code completed, returning to event loop ***
9 2.010: ** longRunningTaskProm().then(handler) called **
10 2.010: Start running .then() handler in longRunningTaskPromResolve()
11 3.010: About to return from .then() in longRunningTaskPromResolve()
12 3.010: ** longRunningTaskPromResolve().then(handler) called **
13 3.012: 1ms setTimeout Got to Run
Here's a step-by-step annotation:
Things start.
longRunningTask() initiated.
longRunningTask() completes. It is entirely synchronous.
longRunningTaskProm() initiated.
longRunningTaskProm() calls resolve(). You can see from this that the promise executor function (the callback passed to new Promise(fn)` is entirely synchronous too.
longRunningTaskPromResolve() initiated. You can see that the handler from longRunningTaskProm().then(handler) has not yet been called. That has been scheduled to run on the next tick of the event loop, but since we haven't gotten back to the event loop yet, it hasn't yet been called.
We're now setting the 1ms timer. Note that this timer is being set only 1ms after we started longRunningTaskPromResolve(). That's because longRunningTaskPromResolve() didn't do much yet. It ran Promise.resolve().then(handler), but all that did was schedule the handler to run on a future tick of the event loop. So, that only took 1ms to schedule that. The long running part of that function hasn't started running yet.
We get to the end of this sequence of code and return back to the event loop.
The next thing scheduled to run in the event loop is the handler from longRunningTaskProm().then(handler) so that gets called. You can see that it was already waiting to run since it ran only 1ms after we returned to the event loop. That handler runs and we return back to the event loop.
The next thing scheduled to run in the event loop is the handler from Promise.resolve().then(handler) so we now see that that starts to run and since it was already queued, it runs immediately after the previous event finished.
It takes exactly 1000ms for the loop in longRunningTaskPromResolve() to run and then it returns from it's .then() handler which schedules then next .then() handler in that promise chain to run on the next tick of the eventl loop.
That .then() gets to run.
Then, finally when there are no .then() handlers scheduled to run, the setTimeout() callback gets to run. It was set to run in 1ms, but it got delayed by all the promise action running at a higher priority ahead of it so instead of running 1ms, it ran in 1004ms.

Why does 2nd JS async function call wait for 1st to finish?

I was under the impression that a call to an async function would not be blocking unless the await keyword is used. However for the following code:
EDIT: I am editing the example to better illustrate my question.
function sum1Through(n) {
console.log('Starting computation for ' + n);
let result = 0;
for (let i = 1; i <= n; i++) {
result += i;
}
console.log('Finished computation for ' + n);
return result;
}
function asyncSum1Through(n) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(sum1Through(n));
}, 0);
});
}
asyncSum1Through(1000000000);
asyncSum1Through(2);
sum1Through(3);
The order of output is:
Starting computation for 3
Finished computation for 3
Starting computation for 1000000000
Finished computation for 1000000000
Starting computation for 2
Finished computation for 2
Why is the 2nd call blocked until the 1st call completes but the 3rd call is not?
If your endpoint is console.log() then obviously "all of your functions" are asynchronous. Not only for one reason but two.
In the asyncSum1Through() function the console.log() instructions are invoked within setTimeout's callback which makes them asynchronously invoked.
Even in sum1Through(3) function console.log() is by default invoked asynchronously but this is not our concern here.
So the output is very "as expected". Whereas the last instruction; namely... sum1Through(3); which happens to be synchronous up until console.log(), comes out first (since console.log() registers into the event queue first) and then the others follow as in the order they have entered into the event queue.
In fact it could have been more convoluted if you hadn't mixed in setTimeout() into your promises but that's totally another topic called microtasks.
Note : Oh..! If your question is; amongst the two asnc functions why the second waits for the first one to finish although the second one would take much less time to run? Then you should understand that asynchronous tasks does not have any privilage on the CPU work currently being consumed by the CPU thread. JS is a single threaded language and any task gets the right to render first has to finish first before the next. Asynchronicity in JS is meant tasks grant priority when other are waiting idle for an IO action which do not own the CPU thread JS running on.
Using an async function doesn't mean that it will be executed "asynchronously". What it offers is that using the await it can be paused and wait for something.
Async Function
An async function can contain an await expression, that pauses the
execution of the async function and waits for the passed Promise's
resolution, and then resumes the async function's execution and
returns the resolved value.
All the code in your function is synchronous and runs immediately, before the call returns the promise. You would need to actually await something to have the code after the await run later.
Async functions are not starting a thread or running anything in parallel, and they cannot block their caller either. They just provide a simple syntax to write sequential code with promises.

Categories

Resources