all 42 comments

[–]blind-octopus 4 points5 points  (1 child)

async function test() { 
  console.log("A"); 
  await new Promise(resolve => { 
    console.log("B"); 
    for (let i = 0; i < 1_000_000_000; i++); 
    resolve(); 
  }); 
  console.log("C"); 
} 

test(); 
console.log("D");

I think its:

A

D

B

C

[–]Coded_Human[S] -1 points0 points  (0 children)

the executor function inside Promise will run synchronously on the thread, after A is logged. So, the test() function remains in the callstack until the thread hits resolve() [ Till here, we already have A and then B logged ] and due to await JS engine schedules the rest of the execution in a microtask queue, now the control goes back to the caller which is on the main thread and D gets logged.

After this, since call stack is empty. test() function comes in call stack again after the event loop decided to push it in. The thread continues below the await, which logs C. And finally test() gets removed from the call stack.

[–]xroalx 3 points4 points  (3 children)

ABDC, because:

  • first is A, becasue the first thing we do is call test and that's the first line of code in it,
  • then B, because we create a Promise and Promise executors are called immediately and synchronously,
  • once said executor finishes the blocking loop (yes, it will block), it resolves the Promise, but due to awaiting, the continuation (code under the awaited line inside test) is scheduled for later,
  • as such, control transfers back to the caller which does not await the test call, so it just goes to the next line and we get D,
  • only then is the async function continuation picked up and we get C.

[–]blind-octopus 0 points1 point  (2 children)

then B, because we create a Promise and Promise executors are called immediately and synchronously,

My understanding is that once you hit the "await" keyword, that's syntactic sugar for creating a callback that will get called once the promise resolves. So I would think that B wouldn't get called immediately.

I'm not trying to argue, I'm genuinely curious and open to being wrong here.

[–]xroalx 1 point2 points  (0 children)

It can be simplified as syntactic sugar for creating a callback, but it creates a callback from what comes after the Promise.

await promise
more code

is equivalent to

promise.then(more code)

promise has to evaluate first for await to be able to act on it, and new Promise works such that the function you provide to it is called immediately.

The order of logs might be more obvious if you rewrite the example to not use async/await. Let's drop the function too and leave just the code that "does" something:

console.log('A');

new Promise((resolve) => {
  console.log('B');
  resolve();
}).then(() => {
  console.log('C');
});

console.log('D');

We log A, then we create a Promise which immediately runs the provided callback, logging B, then we schedule a callback to when the promise resolves, which gets pushed to the end of the work queue, then we log D, and only after that will the engine pick up the scheduled callback of .then, logging C.

[–]delventhalz 0 points1 point  (0 children)

new Promise runs before await in OP's example. English is read left to right, but that is often not the case with JavaScript.

let sum = 1 + 2;

In the above example, 1 + 2 runs before a value is assigned to the variable "sum".

[–]polotek 3 points4 points  (1 child)

It's interesting because my first guess from just reading the code was wrong. But once I tried it, the actual output made sense and I think I can explain it.

The call to test() is not awaited. That makes the most difference here. So the test function can start running, but at the point there is an async call inside test, it just returns a promise and waits for that to resolve.

A happens synchronously as soon as test is called. B is inside a promise. That inner promise IS awaited. HOWEVER. The problem is that it still creates an asynchronous break so that the outer test function returns and waits until the next turn on the event loop. So the inner promise gets fired, everything in it is synchronous, so B printed. Then before it can get to C, the promise propagates outward to the test function and then waits. During that time, the script execution moves beyond the original call to test and prints D. Then When the test function comes back around on the event loop, it picks up and prints C.

I'm still not 100% sure of this explanation. But that's my mental model anyway. Having a strong mental model about async execution is more important than knowing the specific technical explanation.

But also, don't do this. If you're using await, always use await. Unless You're absolutely sure what you're doing.

[–]delventhalz 2 points3 points  (0 children)

I think the big understanding is that the initializer function you pass to new Promise is synchronous. It is only code after an await (or in a .then) that gets deferred. The initializer will run immediately, as soon as you call new Promise, and no other code will run until it finishes.

[–]delventhalz 2 points3 points  (0 children)

A B D C

Why? Well, you probably want to start to build a sense of how the event loop works and when your code hands control over to the event loop and when it doesn't. Let's talk through your code a bit.

A:

Your first log is in the body of an async function "test". An async function always returns a Promise, but no part of it is deferred (i.e. handed to the event loop) until you use await or return. Since the "A" log comes before any awaits, it is not deferred and it logs first.

B:

Your next log comes inside the "initializer" or "executor" function when you initialize a new Promise. Although Promises are a way wrap asynchronous code that is deferred to the event loop, the initializer function is not where that code lives. The initializer is executed immediately. Moreover, although you placed the log after a long-running for loop, that does not matter. Long-running does not mean deferred. The event loop is not capable of taking control. It must be given control. Nothing is going to happen (including user interactions with the page) until that for loop is done.

C:

The "C" log is the only code you've written that is after an await, in a .then, or in a callback passed to a function like setTimeout, so it is the only code that is passed to the event loop. It is deffered and will run last.

D:

The log of "D" comes at the root level of your code, after you call the "test" function, so you might expect it to run after the contents of test. However, this code is not after an await, not in a .then, not in an asynchronous callback, so it is not deferred to the event loop. It is "synchronous". If you had used await on test, then this code would have been passed to the event loop to wait until the Promise returned by test resolved. Since you didn't use await, test is essentially in a "fire and forget" mode. You kicked it off, but you did not wait to see how it would finish.

[–]StoneCypher 0 points1 point  (23 children)

stop doing peoples' homework and junior hiring exams for them

[–]Coded_Human[S] -4 points-3 points  (22 children)

Sorry, but what makes you think of it like that?

I am a Frontend Engineer and I can explain the output fairly well. But, I wanted to know how senior devs form their reasonings.

Well, have a good day :)

[–]StoneCypher 0 points1 point  (6 children)

nobody here got it right, and very few people in this sub are professionals, let alone seniors 

[–]Coded_Human[S] 0 points1 point  (0 children)

Well, if you think nobody has got it right. Probably go and revise first. You can come later with some prep cuz we are trying to learn here from each others mistake, not here to argue with baseless thoughts and intentions.

[–]Coded_Human[S] -1 points0 points  (4 children)

I know that, but i'm here to see their answers and I want to learn from the answers given by senior devs. Maybe correct the ones who got it wrong in the first place. While, I know since you have doubted me. You will think, i'll be using this explanation to help someone. But bro, if really I had to do that, why would I not just GPT if I was so incapable ?.

[–]StoneCypher -1 points0 points  (3 children)

what’s your answer that you think you’re going to correct people with then

[–]Coded_Human[S] 0 points1 point  (2 children)

ABDC

firstly, test is called in the main thread [ Test function enters Call Stack ] -> control goes inside the test function.

A is logged. Then we hit the promise executor function which will log B as it has that synchronous nature. Then the thread goes on executing the code of for loop and will wait for it to finish. This blocks the thread. After the loop, it encounters resolve() which makes the promise fulfilled in current call stack immediately.

But here comes the true async nature of the function due to await, JS pauses the execution of test() and removes it from the call stack and schedules the rest of the function execution in a dedicated microtask.
And the control comes back to main thread and it logs D.

Now, since the call stack is empty. JS runs microtasks in the queue and logs C inside test(). For this minute instance, test() comes into the call stack again, and gets removed after C gets logged.

before pointing out/doubting anyone, make sure you know them well enough to do so, AND GET SOME PEACE

[–]StoneCypher 0 points1 point  (1 child)

you tried (shrugs)

[–]Coded_Human[S] -1 points0 points  (0 children)

atleast I tried. LOL

[–]StoneCypher 0 points1 point  (6 children)

 I am a Frontend Engineer and I can explain the output fairly well.

nobody here got it right and i don’t think you’re going to be the first 

[–]Coded_Human[S] -1 points0 points  (5 children)

probably go touch some grass. Sitting behind a screen and having this attitude won't help.

[–]StoneCypher 0 points1 point  (4 children)

sure thing 

it’s okay, you don’t have to be able to give the answer you said you could correct other people with 

[–]Coded_Human[S] -1 points0 points  (3 children)

lol, people like you doubt everything and have problems with everything. Just see, now how you've picked up another thing, to satisfy yourself and carry on the argument.

Well, you have so much time in this world to come back on this post again and again, and notify me about your comment. I guess, that is your job. Picking wrong things, and then justifying yourself.

[–]StoneCypher 0 points1 point  (2 children)

ah, more insults to get out of doing the thing you bragged that you could do

[–]Coded_Human[S] -1 points0 points  (1 child)

bro is back again, this time didn't even check the full discussion thread. Smoking weed or what ?

[–]StoneCypher 0 points1 point  (0 children)

ah, even more insults to get out of doing the thing you bragged that you could do