JavaScript Promises and Async Programming

(Handle asynchronous tasks like API calls, delays, and file loading)


Why Asynchronous Programming?

JavaScript is single-threaded, meaning it executes one task at a time.

But real-world tasks (like fetching data or waiting for a timer) can take time.

Asynchronous programming lets your code continue running without blocking.

Common Async Tasks in JavaScript

  • Fetching data from an API
  • Reading/writing files (in Node.js)
  • setTimeout / setInterval
  • Event handling

Callbacks (Old Method)

function getData(callback) {
  setTimeout(() => {
    callback("Data loaded");
  }, 1000);
}
                                
getData((result) => {
  console.log(result); // Data loaded
});

⚠️ Problem: Callback Hell — nested callbacks are hard to read and debug.

Promises (Modern Way)

A Promise is an object that represents the future value of an async operation.

States of a Promise:

  • Pending
  • Fulfilled
  • Rejected

Creating a Promise

const myPromise = new Promise((resolve, reject) => {
  let success = true;
  setTimeout(() => {
    if (success) resolve("Success!");
    else reject("Error occurred");
  }, 1000);
});

Consuming a Promise

myPromise
  .then((value) => {
    console.log(value); // Success!
  })
  .catch((error) => {
    console.error(error);
  })
  .finally(() => {
    console.log("Promise completed");
  });

Real Example with fetch()

fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((err) => console.error(err));

Async/Await (Syntactic Sugar for Promises)

async makes a function return a promise.

await waits for the promise to resolve.

async function getPost() {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
    const data = await response.json();
    console.log(data);
  } catch (err) {
    console.error("Error:", err);
  }
}
                                
getPost();

More readable than .then() chaining.

setTimeout / setInterval (Delay functions)

setTimeout(() => {
  console.log("Runs after 2 seconds");
}, 2000);
                                
let counter = 0;
const intervalId = setInterval(() => {
  counter++;
  console.log(counter);
  if (counter === 3) clearInterval(intervalId);
}, 1000);

Promise.all and Promise.race

const p1 = Promise.resolve("First");
const p2 = Promise.resolve("Second");
                                  
Promise.all([p1, p2]).then(values => console.log(values)); // [ "First", "Second" ]
                                  
Promise.race([p1, p2]).then(value => console.log(value)); // "First" (whichever resolves first)

Summary

  • Promises handle async tasks more cleanly than callbacks.
  • async/await makes code cleaner and easier to read.
  • Always use catch() or try...catch to handle errors.
  • Promise.all waits for all promises; Promise.race returns the first one.

🧪 Practice Exercise:

Task:

  1. Create a Promise that resolves after 2 seconds and logs a message.
  2. Use fetch() to get a list of users from a public API.
  3. Write an async function that uses await to fetch and display data.
  4. Try catching errors using try...catch with async/await.
  5. Chain multiple .then() methods to process fetched data.