LearningJavaScript TutorialAsync JavaScript & Promises
CHAPTER 15 · ASYNCHRONOUS FLOW

Async JavaScript & Promises

JavaScript ko network requests, timers aur other delayed work handle karna padta hai bina page ko unnecessarily block kiye. Is chapter me callbacks se Promises aur async/await tak asynchronous flow ko step by step samjhenge.

English + Hinglish50 min readPractice included

Synchronous vs asynchronous code

Synchronous statements normally one after another run karte hain. Asynchronous APIs delayed work start kar sakti hain and JavaScript baaki available code continue kar sakta hai.

order.jsJS
console.log("Start");

setTimeout(() => {
  console.log("Timer finished");
}, 0);

console.log("End");

Typical output is Start, End, then Timer finished. A zero-millisecond delay does not mean “run immediately”.

Hinglish Explanation

Timer ka callback turant current code ko beech me interrupt nahi karta. Current synchronous work complete hone ke baad hi queued callback run hone ka chance milta hai.

Call stack basics

The call stack tracks currently executing JavaScript functions. A function call stack par add hota hai, aur return hone par remove hota hai.

stack.jsJS
function second() {
  console.log("second");
}

function first() {
  second();
}

first();

JavaScript execution model ko samajhne ke liye stack important hai because long synchronous work main thread ko busy rakh sakta hai.

Event loop idea

Browser APIs timers, network operations and events ko host environment me handle kar sakti hain. Jab related callback ready hota hai, it is queued. Event loop ready work ko tab execute karne deta hai when the current JavaScript stack is clear.

Beginner model: JavaScript ek time par current stack ka code execute karta hai, while browser APIs delayed operations handle kar sakti hain.

Tasks and microtasks

Not all queued callbacks same queue use karte hain. Promise handlers are scheduled as microtasks. Timer callbacks are tasks. Current synchronous stack complete hone ke baad pending microtasks are processed before the next task.

queues.jsJS
console.log("A");

setTimeout(() => console.log("Timer"), 0);

Promise.resolve().then(() => {
  console.log("Promise");
});

console.log("B");

Typical order: A, B, Promise, Timer.

Callbacks for async work

A callback is a function passed to another function to run later or when some work completes.

callback.jsJS
function loadLater(callback) {
  setTimeout(() => {
    callback("Course ready");
  }, 500);
}

loadLater(message => {
  console.log(message);
});

Callbacks are fundamental, but deeply nested dependent callbacks can become harder to read and maintain.

What is a Promise?

A Promise is an object representing the eventual success or failure of an asynchronous operation.

  • pending — result not available yet.
  • fulfilled — operation completed successfully.
  • rejected — operation failed.
Settled: A Promise is settled when it is fulfilled or rejected.

Creating a Promise

The Promise constructor receives an executor function with resolve and reject.

promise-create.jsJS
function wait(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}

wait(500).then(() => {
  console.log("500ms completed");
});

Most application code consumes Promises returned by APIs or libraries; you do not need to wrap everything in new Promise().

resolve() and reject()

result.jsJS
function checkScore(score) {
  return new Promise((resolve, reject) => {
    if (score >= 0 && score <= 100) {
      resolve({ score, valid: true });
    } else {
      reject(new Error("Score must be between 0 and 100"));
    }
  });
}
Reject with useful errors: Error objects preserve a message and stack information useful for debugging.

Consuming with then()

then() handles a fulfilled Promise and returns a new Promise, which makes chaining possible.

then.jsJS
checkScore(88)
  .then(result => {
    console.log(result.score);
    return result.score + 5;
  })
  .then(updatedScore => {
    console.log(updatedScore);
  });

Promise chaining

Return the next value or Promise from each then(). The next handler receives that result.

chain.jsJS
getStudent()
  .then(student => getCourses(student.id))
  .then(courses => getProgress(courses[0].id))
  .then(progress => console.log(progress));
Common mistake: If you start an async operation inside then() but forget to return it, the outer chain may continue before that operation finishes.

Handling errors with catch()

catch() handles rejection from earlier Promise steps in the chain.

catch.jsJS
checkScore(140)
  .then(result => console.log(result))
  .catch(error => {
    console.error(error.message);
  });

Errors thrown inside a then() handler also reject the Promise returned by that handler.

finally()

finally() runs after settlement whether the Promise fulfilled or rejected. It is useful for cleanup such as hiding a loading indicator.

finally.jsJS
startOperation()
  .then(result => console.log(result))
  .catch(error => console.error(error))
  .finally(() => {
    console.log("Operation finished");
  });

async functions

An async function always returns a Promise. Returning a normal value automatically fulfills that Promise with the value.

async.jsJS
async function getGreeting() {
  return "Hello";
}

getGreeting().then(message => {
  console.log(message);
});

await

await pauses that async function until the awaited Promise settles. It does not freeze the whole browser page while the Promise is pending.

await.jsJS
async function showProgress() {
  const student = await getStudent();
  const courses = await getCourses(student.id);
  console.log(courses);
}
Simple idea

await Promise-based flow ko synchronous-looking style me likhne deta hai, but function still asynchronous hi rehta hai.

try/catch with async/await

A rejected awaited Promise throws inside the async function, so try/catch can handle it.

try-catch.jsJS
async function loadDashboard() {
  try {
    const student = await getStudent();
    const courses = await getCourses(student.id);
    console.log(courses);
  } catch (error) {
    console.error("Could not load dashboard", error);
  }
}

try/catch/finally

cleanup.jsJS
async function saveProgress() {
  showLoading();

  try {
    await sendProgress();
    showSuccess();
  } catch (error) {
    showError(error);
  } finally {
    hideLoading();
  }
}

Sequential vs parallel awaits

If operations are independent, starting them one by one may unnecessarily increase total wait time.

parallel.jsJS
const profilePromise = getProfile();
const coursesPromise = getCourses();

const profile = await profilePromise;
const courses = await coursesPromise;

Both operations start before the first await. If second operation depends on first result, sequential await is correct.

Promise.all()

Promise.all() waits for all input Promises to fulfill and returns results in input order. If any input rejects, the returned Promise rejects.

all.jsJS
const [profile, courses, notifications] = await Promise.all([
  getProfile(),
  getCourses(),
  getNotifications()
]);
Use when: Results are all required and operations can run independently.

Promise.allSettled()

Promise.allSettled() waits for every Promise and reports each result as fulfilled or rejected instead of failing fast.

all-settled.jsJS
const results = await Promise.allSettled([
  loadProfile(),
  loadRecommendations(),
  loadAnnouncements()
]);

results.forEach(result => {
  console.log(result.status);
});

Promise.race() and Promise.any()

  • Promise.race() settles with the first input Promise that settles, whether fulfilled or rejected.
  • Promise.any() fulfills with the first successful input and rejects only when all inputs reject.

These methods solve specific coordination problems; Promise.all() is more common for beginner application flows.

A reusable delay helper

delay.jsJS
const delay = ms => new Promise(resolve => {
  setTimeout(resolve, ms);
});

async function demo() {
  console.log("Waiting...");
  await delay(1000);
  console.log("Done");
}

This pattern is useful for demos and controlled timing. Real network calls should not be simulated in production with arbitrary delays.

Unhandled rejections

If a Promise rejects and no code handles the rejection, the environment can report an unhandled rejection. Every meaningful async flow should define where failure is handled or intentionally propagated.

Do not silently swallow errors: Empty catch blocks make debugging harder and can hide real failures.

Async UI states

Real browser interfaces usually need explicit loading, success, empty and error states.

ui-state.jsJS
async function loadCourseList() {
  setStatus("Loading courses...");

  try {
    const courses = await getCourses();
    renderCourses(courses);
    setStatus(courses.length ? "" : "No courses found");
  } catch (error) {
    setStatus("Could not load courses");
  }
}

Accessible interfaces should expose meaningful status text and avoid relying only on spinners or color.

Common beginner mistakes

  • setTimeout(..., 0) ko immediate execution samajhna.
  • Promise result ko synchronous value ki tarah use karna.
  • then() chain me next Promise return karna bhoolna.
  • await ko non-async function ke ordinary code me use karna.
  • Independent requests ko unnecessarily sequentially await karna.
  • Every async function me error ignore karna.
  • new Promise() se already-Promise-based APIs ko unnecessary wrap karna.
  • Long synchronous CPU work ko async keyword se automatically non-blocking samajhna.
  • Loading/error UI states skip karna.

Beginner best practices

  • Promise return/await flow ko explicit rakho.
  • Dependent work sequentially, independent work parallel run karo.
  • Errors ko useful context ke saath handle ya rethrow karo.
  • Cleanup ke liye finally use karo when appropriate.
  • Async functions small and single-purpose rakho.
  • UI me loading, empty, success aur failure states plan karo.
  • Promise combinators ko intention ke basis par choose karo.
  • Network work ko next chapter ke Fetch API ke saath combine karenge.

Chapter checklist

  • Synchronous vs asynchronous execution ka basic difference clear hai?
  • Call stack aur event loop ka beginner model samajh aaya?
  • Promise pending, fulfilled aur rejected states explain kar sakte ho?
  • then(), catch() aur finally() use kar sakte ho?
  • async function aur await ka behavior clear hai?
  • try/catch se rejected await handle kar sakte ho?
  • Sequential vs parallel async work ka difference samajh aaya?
  • Promise.all() aur Promise.allSettled() ka purpose clear hai?

Practice Task — Async Learning Dashboard

Fake async helpers use karke dashboard data flow practice karo.

  1. delay(ms) Promise helper banao.
  2. getStudent() async function banao jo delay ke baad student object return kare.
  3. getCourses() async function banao jo course array return kare.
  4. getNotifications() async function banao.
  5. Promise then/catch style se ek function consume karo.
  6. Same flow ko async/await me rewrite karo.
  7. Ek rejected Promise create karke try/catch se handle karo.
  8. finally me loading state reset karo.
  9. Independent profile/courses calls ko parallel start karo.
  10. Promise.all() se multiple required results collect karo.
  11. Promise.allSettled() se partial failures inspect karo.
  12. Console me sync, Promise microtask aur timer ordering observe karo.
  13. Loading, empty aur error message states ka DOM plan likho.
  14. Final code me intentionally forgotten return bug add karke chain behavior debug karo.