DEV Community

Cover image for Why JavaScript Doesn’t Wait: Sync vs Async Explained
Hariharan S J
Hariharan S J

Posted on

Why JavaScript Doesn’t Wait: Sync vs Async Explained

1.Introduction

JavaScript is a single-threaded language, which means it can do only one task at a time. But if that’s true, how does it handle things like API calls, timers, or file loading without freezing the entire application?

That’s where Synchronous and Asynchronous programming comes in.

In this blog, we’ll break down:

  • What synchronous and asynchronous code means

  • Why JavaScript “doesn’t wait”

  • How it actually works behind the scenes

Before Going into the synchronous and asynchronous You are all having one question which is why Javascript is a single-threaded language?

JavaScript is single-threaded because:

It has one call stack, meaning it can execute only one task at a time.

But WHY was it designed like this?

1. Simplicity (Main Reason)

When JavaScript was created (by Brendan Eich):

  • It was meant to run inside the browser

  • Mainly to handle:

  1. Button clicks

  2. Form validation

  3. Simple UI interactions

So, making it single-threaded:

  • Keeps things simple

  • Avoids complex issues like thread synchronization

JavaScript is single-threaded because it uses a single call stack to execute code one task at a time. This design simplifies execution and avoids concurrency issues like race conditions, while asynchronous behavior is handled using the event loop and browser APIs

A Simple Program for Single Thread Behaviour

console.log("Match Started");

function batting() {
    console.log("Batsman started batting");

    for (let i = 0; i < 1e9; i++) {} // playing long innings

    console.log("Batsman finished batting");
}

function bowling() {
    console.log("Bowler started bowling");
}

batting();
bowling();

console.log("Match Ended");
Enter fullscreen mode Exit fullscreen mode

Output

Match Started
Batsman started batting
Batsman finished batting
Bowler started bowling
Match Ended
Enter fullscreen mode Exit fullscreen mode

Explanation

  • First, batsman plays completely

  • Only after that, bowler starts bowling

  • Both don’t happen at the same time

  • One after another execution

Just like in cricket where batting must finish before bowling begins in this example, JavaScript executes one task at a time. Even if multiple functions are called, the next task waits until the current one finishes, proving that JavaScript is single-threaded

Here is Where Synchronus and Asynchronus Comes to play

2.What is Synchronous in JavaScript?

Before going into the technical part of synchronus we shall understand the synchronus non technically

Imagine you are eating chocolates 🍫

  • You eat one chocolate

  • Only after finishing it → you take the next chocolate

You don’t eat 2 chocolates at the same time

Likewise in Javascript

Computer also does the same:

  • It does one work

  • Waits till it finishes

  • Then does the next work

Now you get a idea of what synchronus actually do if we deep dive into the technical part you can clearly understand about this topic

JavaScript executes code line by line using a single call stack, where each operation must complete before the next one starts.

Single Thread + Call Stack

JavaScript is single-threaded, so it uses:

One Call Stack (Execution Stack)

Example

function starter() {
    console.log("Starter is served 🍲");
}

function mainCourse() {
    console.log("Main course is served 🍛");
}

function dessert() {
    console.log("Dessert is served 🍰");
}

starter();
mainCourse();
dessert();
Enter fullscreen mode Exit fullscreen mode

Output

Starter is served 🍲
Main course is served 🍛
Dessert is served 🍰
Enter fullscreen mode Exit fullscreen mode

Explanation:

First, you eat starter 🍲

Then main course 🍛

Finally dessert 🍰

You don’t eat everything at the same time

One after another → synchronous

Blocking Nature

Synchronous code is blocking

Example

console.log("Start");

function heavyTask() {
    for (let i = 0; i < 1e9; i++) {} // long work
    console.log("Heavy Task Done");
}

heavyTask();

console.log("End");
Enter fullscreen mode Exit fullscreen mode

What happens?

  • JS starts execution

  • Enters heavyTask()

  • Stays there until loop finishes

  • Only then moves to next line

Everything else is blocked

Execution Context

Whenever a function runs:

JavaScript creates an Execution Context

Each context has:

  • Memory (variables)

  • Code execution phase

Flow:

  • Global Execution Context created first

  • Then function contexts are created and pushed to stack

Real-World Impact

Problems with synchronous code:

  • UI freezing

  • Slow performance

  • Poor user experience

Example:

  • Loading large data

  • Heavy calculations

  • File processing

*The Problems in Synchronus is Solved in Asynchronus So lets deep dive into Asynchronus Concept *

3.What is Asynchronous JavaScript

Asynchronous execution means:

JavaScript can delegate long-running tasks and continue executing other code, instead of blocking the main thread.

How JavaScript Actually Handles Async

JavaScript runtime consists of:

  • Call Stack

  • Web APIs (Browser / Node APIs)

  • Callback Queue (Task Queue)

  • Event Loop

console.log("Match Started 🏏");

setTimeout(() => {
    console.log("Drinks break over 🍹, match resumes");
}, 2000);

console.log("First over is going on...");
Enter fullscreen mode Exit fullscreen mode

Output

Match Started 🏏
First over is going on...
Drinks break over 🍹, match resumes
Enter fullscreen mode Exit fullscreen mode

Explanation

Match starts

First over continues immediately

Meanwhile:

  • Drinks break is scheduled ⏳ (setTimeout)

JavaScript does NOT wait

It continues the match

After 2 seconds:

  • Drinks break message comes

What This Shows

Even though drinks break was set:

  • Match didn’t stop

  • Other things continued

This is asynchronous behavior

Event Loop (Heart of Async)

The event loop continuously checks:

  • Is Call Stack empty?

  • If yes → take task from queue → execute

This is why JS feels non-blocking

Types of Async Tasks

Macro Tasks (Callback Queue)

  • setTimeout

  • setInterval

  • DOM events

Micro Tasks (Priority Queue)

  • Promises

  • queueMicrotask

Microtasks run before macrotasks

Okay this will lead up to the methods of asynchronus in Javascript

4.Methods Used in Asynchronous JavaScript

In JavaScript, async behavior is achieved using different techniques (methods):

What is a Promise in JavaScript

A Promise in JavaScript is an object that represents the eventual completion or failure of an asynchronous operation, allowing developers to handle results using methods like then, catch, and finally.

Basic Structure of a Promise

const promise = new Promise((resolve, reject) => {
    // async work
});
Enter fullscreen mode Exit fullscreen mode

It has 2 important functions:

  • resolve() → success

  • reject() → failure

Promise States

A Promise has 3 states:

  • Pending → Initial state

  • Fulfilled → Success (resolve called)

  • Rejected → Failed (reject called)

const foodOrder = new Promise((resolve, reject) => {
    let foodReady = true;

    if (foodReady) {
        resolve("Your food is ready ");
    } else {
        reject("Sorry, food is not available ");
    }
});

foodOrder
    .then(message => console.log(message))
    .catch(error => console.log(error));
Enter fullscreen mode Exit fullscreen mode

Output (if foodReady = true)

Your food is ready 
Enter fullscreen mode Exit fullscreen mode

Output (if foodReady = false)

Sorry, food is not available 
Enter fullscreen mode Exit fullscreen mode

Explanation

You order food

Kitchen checks:

  • If food is ready → serve it (resolve)

  • If not → say unavailable (reject)

Customer (your code):

  • .then() → handles success

  • .catch() → handles failure

A Promise in JavaScript is like ordering food—either the food gets delivered successfully (resolve) or the order fails (reject), and we handle both outcomes using then and catch.

Promise Chaining

You can chain multiple .then()

function getData() {
    return new Promise(resolve => {
        setTimeout(() => resolve(10), 1000);
    });
}

getData()
    .then(num => num * 2)
    .then(num => num + 5)
    .then(result => console.log(result)); // 25
Enter fullscreen mode Exit fullscreen mode

Each .then() passes value to next

async / await(To Be Discussed)

fetch() API(To Be Discussed)

Top comments (0)