Skip to content
Advertisement

Are javascript’s async functions actually synchronous?

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 awaits 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).

Advertisement