Why is this async function not behaving asynchronously?
Asked Answered
F

6

5

I am learning Javascript. I am not used to asynchronous programming so having a hard time here. Here's my code:

var x = 0;

async function increment(x) {
  ++x;
  for (var i = 0; i < 999999999; i = i + 1);
  console.log('value of x is: ' + x);
};

increment(x);

console.log('Out of the function');

Expectation: 'Out of the function' gets printed instantly. 'value of x...' takes some time.
Reality: 'value of x...' gets printed first after a delay. Then 'Out of the function' is printed.

My question is, why isn't increment() being called asynchronously? Why is it blocking the last console.log?

Frigate answered 30/9, 2020 at 8:19 Comment(4)
Most of the answers seem to be about a solution. But I was rather looking forward to some explanations to why my expectation is not correct. I thought whenever I use async, that function will NOT block next synchronous execution - irrespective of the use of await. I just don't want increment function to block last console.log()Frigate
If you put async without await, the function will behave like a normal (synchronous) function. So, increment(x) will block last console.log(). I have put a link to MDN in my answer, may be its helpful to understand better.Eucharist
@FarhanFuad check out my answer, especially the Bob-Alice part.Video
It happens because at no point in your function you ever do something asynchronous, you never yield execution back to the event loop. Keep in mind that Javascript is single-threaded. If you insert something like await new Promise(setImmediate) after every 1000th iteration or so, you'll give some other code (like your console.log) a chance to execute instead.Wooldridge
V
11

TL;DR: You must await something inside the async function to make it async.

var x = 0;

async function increment(x) {
  await null;  // <-- ADD THIS LINE
  ++x;
  for (var i = 0; i < 10; i = i + 1);
  console.log('value of x is: ' + x);
};

increment(x);

console.log('Out of the function');

Longer version:

First let's clarify one thing: JS runtime is an event-loop based single threaded runtime, this mean no concurrency/paralell code execution (unless you use Worker or child_process, that's another topic).

Now to the asynchronous part. The async-await is just a handy syntax sugar that under the hood uses Promise and Generator, which in turn calls the event loop scheduling API provided by platfrom. In Node.js that API is process.nextTick while in browser it's setImmediate.

Through these APIs jobs are scheduled, buffered in the micro task queue, which will be tapped and executed by the event loop when idle. Thus the asynchronicity is in fact just scheduling.


With this knowledge let's come back to your case.

the async function keyword does two things:

  1. it forces the function to return a Promise
  2. it allows you to use the await keyword inside the function body

The actually scheduling part happens when you use the await keyword. What it does, is to send a scheduled task to the micro task queue, which will always be a callback to a Promise.then() call (in above example I send null, it'll be auto-wrapped to be Promise.resolve(null)), then pause the execution of current function, and wait until that promise resolves to continue execution of the rest.

So if you don't involve the event-loop scheduler by using await keyword, then execution order will stay normal. Thus explain your case.


If you do undertand that async function is just syntax sugar around Promise, the next often seen misunderstanding is the execution order of new Promise(callback).

Case 1:

new Promise((resolve) => {
  console.log('Bob comes first!')
  resolve(null)
})

console.log('Alice comes first!')

Q: Who comes first?
A: Bob comes first.

Case 2:

new Promise((resolve) => {
  resolve(null)
}).then(() => console.log('Bob comes first!'))


console.log('Alice comes first!')

Q: Who comes first?
A: This time Alice comes first.

Case 1 corresponds to your code. async wrap the function into a promise's callback, but if you don't .then() that callback is executed immediately, in normal order.

Case 2 corresponds to my await null example. Which breaks up the function body into two parts, one inside new Promise(callback), the rest inside promise.then(callback). And the execution order of the latter is deferred.

Video answered 30/9, 2020 at 9:4 Comment(0)
E
2

async function has no await, so function will run synchronously. If no await is provided, javascript does not throw any error.

From MDN

The body of an async function can be thought of as being split by zero or more await expressions. Top-level code, up to and including the first await expression (if there is one), is run synchronously. In this way, an async function without an await expression will run synchronously

Convert it to something like below and you will see expected behaviour:

 var x = 0;
async function increment(x) {
  ++x;
  await setTimeout(function() {
    for (var i = 0; i < 999999999; i = i + 1);
  });
  console.log('value of x is: ' + x);
};

increment(x);

console.log('Out of the function');
Eucharist answered 30/9, 2020 at 8:35 Comment(5)
@adiga, It is an example to convey the concept and I believe it's serving the purpose.Eucharist
@adiga, you mean if I remove await, it will still log "Out of the function" before "value of x"?Eucharist
oh, no worries mate. I thought I missed something conceptually.Eucharist
This is a misleading answer. You use setTimeout to tap into event-loop scheduling API, which would indeed give you async code execution order, but that has nothing to do with async-await mechanism. Putting the two together is very confusing.Video
You can await whateverAsLongAsItsAnExpression to make this example work.Video
T
0

Because JavaScript is not multithreaded I suppose.

You could try with a JQuery approach (maybe) or learn about Web Workers The very first resource I found googlin' around: https://medium.com/techtrument/multithreading-javascript-46156179cf9a

Theodore answered 30/9, 2020 at 8:32 Comment(0)
M
0

That's because a function being asynchronous doesn't mean that it will always end last.

Wrap the async console.log in a setTimeout and you'll see the last console.log print first.

var x = 0;

async function increment(x) {
  ++x;
  setTimeout(() => {
    console.log('value of x is: ' + x);
  }, 1000);
};

increment(x);

console.log('Out of the function');

Here is a Fiddle: https://jsfiddle.net/Ldjma3s8/

Moorehead answered 30/9, 2020 at 8:37 Comment(0)
S
0

You should use await. From the official JS documentation: "The keyword await makes JavaScript wait until that promise settles and returns its result."

Also if it seems a bit strange but you can achieve your result with this:

const x = 0;
async function f(x) {
  try {
    ++x;
    for (i = 0; i < 999; i = i + 1)
      console.log('value of x is:', await i);

  } catch (e) {
    console.log('error', e);
  }
};

f(x);
console.log('Out');
Surefire answered 30/9, 2020 at 8:40 Comment(2)
Out gets logged before value of x isHonorary
Yes it is basically what he's asksing, that is: "Expectation: 'Out of the function' gets printed instantly. 'value of x...' takes some time."Surefire
N
0

The async keyword makes a function return a promise or 'thenable'. You need to await your promise increment.

Top level await is proposed but until we have it You would need your call to be within another async function or to use then.

https://github.com/tc39/proposal-top-level-await

var x = 0;

async function increment(x) {
  ++x;
  for (var i = 0; i < 999999999; i = i + 1);
  console.log('value of x is: ' + x);
};

async function waitThenLog() {
  await increment(x);
  console.log('Out of the function');
}

waitThenLog();


// or...

increment(x).then(() => console.log('Out of the function'));
Nalchik answered 30/9, 2020 at 8:41 Comment(1)
Ironically to achieve the desired effect you could also omit the async on "async function increment" as then the increment call would be blocking.Nalchik

© 2022 - 2024 — McMap. All rights reserved.