Home > Software design >  Why is async function blocking?
Why is async function blocking?

Time:11-10

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?

CodePudding user response:

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 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.

CodePudding user response:

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