Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why isn't Javascript async function immediately returning?

I'm attempting to get a better grasp on async functions and promises in JS. To do this, I wrote a sample program that has the goal of calling a function that does busy work (purposefully not using async setTimeout as I want to mimic a long-running process) but returns immediately. However, I can't seem to figure out why this isn't working.

    test();
    
    async function intense(){
      var start = new Date().getTime();
      for (var i = 0; i < 1e6; i++) {
        if ((new Date().getTime() - start) > 2000){
          break;
        }
      }
      console.log("Done with async work");
    }
    async function test(){
        console.log("Print 1");
        intense(); // does some busy work for a few seconds
        console.log("Print 2"); // want this to print immediately after print 1
    }

When I run it, I get:

Print 1
Done with async work
Print 2

And I would like it to be:

Print 1
Print 2
Done with async work

I thought that it would print the latter sequence because I declared the function intense() to be async, so it would return a promise immediately and continue work asynchronously.

I even tried to refactor the intense function to be a promise that resolves immediately, but to no avail.

async function intense(){
  return new Promise((resolve)=> {
      resolve();
      var start = new Date().getTime();
      for (var i = 0; i < 1e6; i++) {
        if ((new Date().getTime() - start) > 2000){
            break;
        }
  }
  console.log("Done with async work");
}, null)
}

What am I missing?

like image 568
rb612 Avatar asked Mar 10 '19 09:03

rb612


3 Answers

There a are a couple of reasons for what you're seeing:

  1. An async function is synchronous up until its first await or return, so the entire function runs before returning in your case.

  2. Busy-waiting isn't asynchronous.

  3. test needs to use await if it's going to wait for intense to complete before continuing.

  4. Moving something into a promise doesn't take it off the thread. The only way to do that in most JavaScript environments (including browsers) is to use a Worker thread (MDN, Node.js docs — Node.js has had Worker since ~v10.5, and while it's still marked "experimental" the main parts of the API should be fairly stable as they're drawn from the web worker standard API).

It's important to remember that promises don't make anything asynchronous¹, they provide a means of observing the result of something that's already asynchronous.

Here's an example using setTimeout for the asynchronous part that help you understand these better:

function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function intense(value) {
    console.log("intense(" + value + ") - This is synchronous");
    await delay(100);
    console.log("intense(" + value + ") - This is asynchronous, because it's after `await`");
}

async function test(){
    console.log("Print 1");
    intense(1);       // <== WITHOUT await
    console.log("Print 2");
    await intense(2); // <== With await
    console.log("Print 3");
}

test();
.as-console-wrapper {
  max-height: 100% !important;
}

¹ There's one small caveat to that: The handler you pass to then, catch, or finally will always be called asynchronously, even if the promise you're calling them on is already settled. That's literally the only thing that promises actually make asynchronous.

like image 198
T.J. Crowder Avatar answered Nov 15 '22 18:11

T.J. Crowder


so it would return a promise immediately and continue work asynchronously.

No it would not. The callback passed to the Promise constructor is called immeadiately. What is async is the process of calling resolve or reject later on, and how .then chains get called back somewhen.

However it isnt async in the sense that the code runs on another thread or gets deferred, that won't happen as JS itself is executed in a single thread*.

 console.log(1);
 const promise = new Promise((resolve, reject) => {
  console.log(2); // gets executed immeadiately 
 });

 promise.then(() => console.log(4)); // < Promise resolve asynchronously
console.log(3);

*If you plan to do really "intense" work, it might be benefitial to do that in another thread (see WebWorker in browsers and child_process.spawn for NodeJS).

like image 31
Jonas Wilms Avatar answered Nov 15 '22 18:11

Jonas Wilms


This is a misunderstanding that is pretty easy to make with javascript. Your function intense() blocks the thread. Putting something in an async function does not change the fact that you only have one thread in javascript. As soon as the interpreted starts running that for loop it's going to use the one thread to run it until it's over. Nothing else will happen until then.

Async functions don't return immediately, they run the body of the code until the hit an await and return a promise. In your example, the entire function will run before it returns.

You can't use this kind of long running process without blocking. That's why node goes out of its way to offload things like i/o access and timers to another thread.

See here for more details: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/

If you want to run some long-running code like this asynchronously you will need to spawn a child process: https://nodejs.org/api/child_process.html

like image 24
Mark Avatar answered Nov 15 '22 18:11

Mark