I am trying to figure out how does asynchronous code work in Javascript. Now, I understand that there is actually one single thread in JS that executes jobs in a queue, and it can only start executing the next job if the current one is completed (i.e. if all of the sync code or an async function is completed).
Now, the confusing part is what actually counts as an asynchronous function – what actually gets put into a separate job in the queue, and what doesn’t.
For start, we have the async
keyword for functions. So does that mean those functions will be put into a separate job in the queue and be executed somewhere in the future? Well, actually it turns out the answer is NO. But bear with me, as I will explain.
As far as I understand, in theory, the JS thread is supposed to begin by executing all synchronous code until it completes, while delaying the execution of all async functions, promises and callbacks by placing them as jobs to the end of the queue. Then, once all sync code completes, it will start doing all those jobs that got piled up.
So if I have the following code:
async function asyncFunc() { console.log("executing async function"); } console.log("starting sync code"); asyncFunc().then(() => { console.log("executing callback of async function") }); console.log("sync code completed");
Then in theory, it should execute all sync code first, and only then start executing the async function and then the callback:
starting sync code sync code completed executing async function executing callback of async function
But the reality is different! In reality, it actually executes the async function synchronously, together with the rest of the sync code. The only bit that actually gets put into the job queue is the callback of the async function:
starting sync code executing async function sync code completed executing callback of async function
So what does that mean? That async
functions are actually a lie? It seems so, as they are actually normal, synchronous functions that you can happen to attach an async callback to.
Now, I know that async
is actually a syntactic sugar for a function that returns a Promise
, such as:
async function asyncFunc() { console.log("executing async function"); }
is syntactic sugar for:
function asyncFunc() { return new Promise((resolve) => { console.log("executing async function"); resolve(); }); }
But my point still remains. The supposedly-async function that you pass into the promise actually is executed synchronously. Well, technically the Promise
object doesn’t imply that it will be executed asynchronously, but the async
keyword does! So it’s outright false information, it makes you believe that it’s asynchronous, when it demonstrably isn’t.
Advertisement
Answer
Just like when constructing a Promise, anything synchronous inside an async
function before any await
s are encountered will execute synchronously. An async
function will only stop executing its code once it encounters an await
– until then, it may as well be a normal non-async
function (except for the fact that it’ll wrap the return value in a Promise).
async function asyncFunc2() { console.log("in Async function 2"); } async function asyncFunc1() { console.log("in Async function 1"); await asyncFunc2(); console.log('After an await'); } console.log("starting sync code"); asyncFunc1().then(() => { console.log("Received answer from async code"); }); console.log("finishing sync code");
As you can see in the snippet above, the main thread only resumes outside of asyncFunc1
once asyncFunc1
‘s await
(and all synchronous code invoked by that await
) is complete.
async
is a keyword that allows you to use await
inside of a function, but it doesn’t intrinsically mean anything else, really – it’s just a keyword. The function may even run all of its code synchronously (though that’d be kind of weird to see).