Is setTimeout required?

I have a question to async, await and setTimeout(). I thought, I use asynchron functions for slow processes. So I tried it with a large loop. On my Computer, it needs few seconds to run the following code:

function slowFunction() {
    return new Promise(resolve => {
        setTimeout(() => {
            for (let i = 0; i < 4000000000; i++) {};
            resolve('Ready at ' + new Date().toLocaleTimeString('de'));
        }, 0);
    });
};


console.log('Start: ' + new Date().toLocaleTimeString('de'));

(async () => {
    console.log('Call slow function.');
    console.log(await slowFunction());
})();

console.log('There is no need to wait for the slow function: ' + new Date().toLocaleTimeString('de'));

The output is:

Start: 16:39:20
Call slow function.
There is no need to wait for the slow function: 16:39:20
Ready at 16:39:23

And now the question: What is the difference to the next code:

function slowFunction() {
    return new Promise(resolve => {
        for (let i = 0; i < 4000000000; i++) {};
        resolve('Ready at ' + new Date().toLocaleTimeString('de'));
    });
};

console.log('Start: ' + new Date().toLocaleTimeString('de'));

(async () => {
    console.log('Call slow function.');
    console.log(await slowFunction());
})();

console.log('There is no need to wait for the slow function: ' + new Date().toLocaleTimeString('de'));

The output is:

Start: 16:39:20
Call slow function.
There is no need to wait for the slow function: 16:39:23
Ready at 16:39:23

By the first example, it looks like asynchron. By the second example, the function wait for the end of loop.

Do I have to use setTimeout or do I have an error in the code or am I getting it wrong? I both cases, the resolve statment is behind the large loop.

The most examples for async and await used setTimeout, but I think, it's just to simulate a break.

Thanks for your help in advance.

Best greets Pascal

Answers:

Answer

TL:DR

Promises and async functions don't offload your code to another thread. If you want to move that long-running process off the main thread, on browsers look at web workers, and on Node.js look at child processes.

Details

Promises and async functions (which are just a syntax for creating and consuming promises) don't move your processing to any other thread, it still happens on the same thread you start the process on. The only thing they do is ensure that then and catch callbacks are called asynchronously. They don't make your code asynchronous (other than that one thing, ensuring the callbacks happen asynchronously).

So your first block using setTimeout just sets a timeout, returns a promise, and then when the timeout expires it blocks the main thread while your slow-running process executes. This just changes when the blocking happens a little bit, it doesn't change the fact of the blocking.

You can see that effect here, notice how the counter pauses when the long-running process occurs:

function slowFunction() {
  return new Promise(resolve => {
    setTimeout(() => {
      const stop = Date.now() + 2000;
      while (Date.now() < stop) {
        // busy wait (obviously, never really do this)
      }
    }, 1000);
  });
};

console.log("before slowFunction");
slowFunction()
  .then(() => {
    console.log("then handler on slowFunction's promise");
  })
  .catch(console.error);
console.log("after slowFunction");

let counter = 0;
const timer = setInterval(() => {
  console.log(++counter);
}, 100);
setTimeout(() => {
  clearInterval(timer);
  console.log("done");
}, 3000);
.as-console-wrapper {
  max-height: 100% !important;
}

Your second block not using setTimeout just blocks right away, because the promise executor function (the function you pass new Promise) runs immediately and synchronously, and you're not doing anything to make it asynchronous.

You can see that here; the counter pauses right away, not later:

function slowFunction() {
  return new Promise(resolve => {
    const stop = Date.now() + 2000;
    while (Date.now() < stop) {
      // busy wait (obviously, never really do this)
    }
  });
};

console.log("before slowFunction");
slowFunction()
  .then(() => {
    console.log("then handler on slowFunction's promise");
  })
  .catch(console.error);
console.log("after slowFunction");

let counter = 0;
const timer = setInterval(() => {
  console.log(++counter);
}, 100);
setTimeout(() => {
  clearInterval(timer);
  console.log("done");
}, 3000);
.as-console-wrapper {
  max-height: 100% !important;
}

We don't even see the before slowFunction log appear until after the long-running code has finished, because the browser never got a chance to repaint, we had the thread hogged.

Regarding async functions: The code in an async function starts out synchronous, and is synchronous until the first await (or other construct, such as setTimeout, that schedules things to execute later). Only the code after that is asynchronous (because it had to wait).

Here's an example demonstrating that:

async function foo() {
  console.log("before await");
  await Promise.resolve();
  console.log("after await");
}

console.log("before foo");
foo()
  .then(() => {
    console.log("then handler on foo's promise");
  })
  .catch(console.error);
console.log("after foo");

Here's the output of that:

before foo
before await
after foo
after await
then handler on foo's promise

Notice how before await occurs before after foo; it's synchronous with the call to foo. But then after await doesn't occur until later (because await Promise.resolve() has to make the code following it occur asynchronously; it's syntactic sugar for then, which promises not to call its handler synchronously even if the promise is already resolved).

Answer

The difference is that this is completely synchronous code:

return new Promise(resolve => {
    for (let i = 0; i < 4000000000; i++) {};
    resolve('Ready at ' + new Date().toLocaleTimeString('de'));
});

This statement will block the JavaScript thread and force it to wait until all of those 4 billion iterations have taken place. Then it will move on to the next statement. Since the console.log executes after this, it will not execute until that loop has finished.

That's why you are seeing a difference.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us Javascript

©2020 All rights reserved.