← Back to list

Sync vs Async JavaScript — Everything You Need to Know

JavaScript is a single-threaded, synchronous language at its core. But in the real world, it powers complex web apps that fetch data, read…

Archit Kamble · 2026-05-08 10:55 · 0 claps · 3.8 min read
#asynchronous-programming #javascript #promises-in-javascript #how-javascript-works
Open on Medium ↗
Wiki topics: 💻 · Programming 🌐 · Web Development

Sync vs Async JavaScript — Everything You Need to Know

JavaScript is a single-threaded, synchronous language at its core. But in the real world, it powers complex web apps that fetch data, read files, and handle user events — all without freezing the browser. How? Through asynchronous programming. Let’s break it all down.

Difference between Sync & Async JS

Synchronous code executes line by line. Each statement waits for the previous one to finish before running. This is simple and predictable — but problematic when a task takes time.

javascript

function sync() {
  console.log("1");
  console.log("2");
  console.log("3");
}
sync();
// Output: 1, 2, 3

The issue? If one operation — say, fetching data from an API — takes 3 seconds, everything after it is blocked. The entire program becomes unresponsive.

Asynchronous code solves this. Long-running tasks are handed off to browser Web APIs or Node.js runtime APIs. Once complete, their results are queued and executed when the main thread is free.

javascript

function asyncExample() {
  console.log("1");
  setTimeout(() => {
    console.log("2");
  }, 2000);
  console.log("3");
}
asyncExample();
// Output: 1, 3, 2

JavaScript doesn’t wait for setTimeout to finish — it moves on immediately, and "2" prints after 2 seconds.

What is a Callback & Callback Hell?

A callback is simply a function passed as an argument to another function, to be executed later.

javascript

function one(callback) {
  console.log("one");
  callback();
}
function two() {
  console.log("two");
}
one(two);
// Output: one, two

Callbacks work well for simple cases. But when you chain multiple asynchronous operations, things get messy fast.

Callback Hell (also called the Pyramid of Doom) happens when callbacks are nested inside callbacks, creating deeply indented, hard-to-read code:

javascript

getUser((user) => {
  console.log(user);
  setTimeout(() => {
    console.log(user.id);
    setTimeout(() => {
      console.log(user.name);
    }, 1000);
  }, 1000);
});

This structure is painful to read, debug, and maintain. This is one of the core problems that Promises were designed to solve.

What are Web Browser APIs?

Web APIs are features built into the browser that allow JavaScript to perform operations outside the main Call Stack — without blocking it.

Common examples:

  • setTimeout() and setInterval() — delay or repeat execution
  • fetch() — make HTTP requests
  • DOM APIs — manipulate the document
  • History API — control browser navigation
  • Location API — access geolocation

These APIs let JavaScript offload time-consuming work, keep the UI responsive, and handle results asynchronously when they’re ready.

What is a Promise & Its Different Methods?

A Promise is an object representing the eventual result of an asynchronous operation. It gives you a cleaner, more structured way to handle async code compared to callbacks.

A Promise has three states:

  • Pending — the operation is still in progress
  • Fulfilled — the operation completed successfully
  • Rejected — the operation failed

javascript

const promise = new Promise((resolve, reject) => {
  const success = true;
  if (success) {
    resolve("Success");
  } else {
    reject("Failed");
  }
});

Promise Methods

**Promise.all()* — Waits for all* promises to resolve. If any single one rejects, it immediately rejects.

javascript

Promise.all([promise1, promise2]);

**Promise.allSettled()* — Waits for all* promises to settle, regardless of whether they resolved or rejected. Useful when you want results from every promise, even the failed ones.

javascript

Promise.allSettled([promise1, promise2]);

**Promise.race()* — Returns the result of whichever promise settles first* — resolved or rejected.

javascript

Promise.race([promise1, promise2]);

**Promise.any()* — Returns the first successfully resolved* promise. If all promises reject, it throws an AggregateError.

javascript

Promise.any([promise1, promise2]);

What is Inversion of Control?

When you pass a callback to a third-party API, you’re handing over control of your own code:

javascript

apiCall(myCallback);

Now you’re trusting that API to call your function correctly — at the right time, the right number of times. It might call it once, multiple times, or never at all. This loss of control over your program’s flow is called Inversion of Control.

Promises solve this by returning a value you control. Instead of trusting someone else to call your function, you decide what to do when the Promise resolves or rejects.

What is the Event Loop?

The Event Loop is the mechanism that makes asynchronous JavaScript possible. It continuously monitors the Call Stack and the task queues.

Here’s the priority order:

Call Stack  →  Microtask Queue  →  Callback Queue
  • Call Stack — executes currently running code
  • Microtask Queue — holds Promise callbacks (.then, .catch)
  • Callback Queue — holds callbacks from Web APIs like setTimeout

javascript

console.log("Start");
setTimeout(() => console.log("Timer"), 0);
Promise.resolve().then(() => console.log("Promise"));
console.log("End");
// Output:
// Start
// End
// Promise
// Timer

Even though setTimeout has a 0ms delay, the Promise callback runs first because the Microtask Queue has higher priority than the Callback Queue.

How do we use the fetch method?

fetch() is a built-in browser API for making HTTP requests. It returns a Promise, making it easy to integrate with .then() chains or async/await.

javascript

fetch("https://api.example.com/users")
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.error(error));

Since most APIs return JSON, we use .json() to parse the response into a usable JavaScript object.

Common HTTP Methods

MethodPurposeGETRetrieve dataPOSTCreate or send new dataPUTReplace existing data entirelyPATCHPartially update existing dataDELETERemove data

Error Handling While Writing Async Code

Errors are inevitable in async operations — network failures, invalid responses, timeouts. Proper error handling keeps your app from silently breaking.

With Promises:

javascript

promise
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.error(error);
  });

With Callbacks (error-first pattern):

javascript

getUser((err, data) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(data);
});

With async/await (recommended):

javascript

async function getData() {
  try {
    const data = await promise;
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}

async/await is the most readable approach — it makes asynchronous code look and behave like synchronous code, while try/catch keeps error handling clean and familiar.

Understanding these concepts — the Event Loop, Promises, callbacks, and async/await — is foundational to writing reliable JavaScript. Once these clicks, the rest of JS async patterns become much easier to reason about.

If you found this helpful, consider following for more JavaScript deep dives.


메타데이터
post_id
2e7b7e8586da
slug
sync-vs-async-javascript-everything-you-need-to-know-2e7b7e8586da
url
https://medium.com/@architkamble001/sync-vs-async-javascript-everything-you-need-to-know-2e7b7e8586da
canonical_url
https://medium.com/@architkamble001/sync-vs-async-javascript-everything-you-need-to-know-2e7b7e8586da
author_url
https://medium.com/@architkamble001
status
ok
fetched_at
2026-07-13 06:23:13