No history yet

Introduction to Asynchronous JavaScript

The Flow of Code

Most JavaScript code runs in a predictable, step-by-step order. It executes the first line, then the second, and so on, never moving to the next line until the current one is finished. This is called synchronous programming.

Think of it like following a recipe. You chop the vegetables before you put them in the pan. You don't start preheating the oven after you've already baked the cake. Each step happens in sequence.

console.log("First task");
console.log("Second task");
console.log("Third task");

If you run this code, the console will always print the logs in that exact order. The second console.log patiently waits for the first one to finish. This works perfectly for many tasks, but it hits a major snag when a task takes time.

When Code Has to Wait

What happens when your code needs to do something that isn't instant? Imagine it needs to fetch data from a server, wait for a user to click a button, or read a large file. These operations can take an unpredictable amount of time.

Lesson image

If JavaScript waited synchronously for these tasks, it would have to pause everything. The entire user interface would freeze. Clicks wouldn't register, animations would stop, and the page would become completely unresponsive. This is known as "blocking" the main thread, and it creates a terrible user experience.

To solve this, JavaScript can execute code asynchronously. It starts a time-consuming task, and instead of waiting for it to finish, it moves on to the next lines of code. The slow task runs in the background. When it finally completes, it lets the program know the result is ready.

The Old Way Callbacks

For a long time, the primary way to handle asynchronous tasks was with callback functions. A callback is a function you provide as an argument to another function, with the instruction to run it later, once the main task is complete.

Callback

noun

A function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action.

Let's see a simple example using setTimeout, a function that waits for a specified time before running some code.

console.log("Start");

// Run this function after 2000 milliseconds (2 seconds)
setTimeout(() => {
  console.log("Inside the timeout!");
}, 2000);

console.log("End");

// Output:
// Start
// End
// Inside the timeout!

Notice the order. JavaScript logs "Start," sets the timer, and immediately logs "End" without waiting. Two seconds later, the callback function runs. This works, but it can get messy when you need to perform several asynchronous operations in a row.

Imagine you need to fetch a user's data, then use that data to fetch their posts, and then use those posts to fetch the comments. With callbacks, you end up nesting functions inside of functions. This leads to a pattern often called "callback hell" or the "pyramid of doom" because of its shape.

getUser(userId, (user) => {
  console.log("Got user:", user);
  getPosts(user.id, (posts) => {
    console.log("Got posts:", posts);
    getComments(posts[0].id, (comments) => {
      console.log("Got comments:", comments);
      // And what if we need to do another thing?
      // The nesting continues...
    });
  });
});

This code is hard to read, difficult to debug, and prone to errors. There had to be a better way.

A Better Way Promises

To clean up the mess of nested callbacks, modern JavaScript introduced Promises. A Promise is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

Think of a Promise like ordering a pizza. When you place your order, you get a receipt. You don't have the pizza yet, but the receipt is a promise that you will get it eventually. The order is "pending."

Eventually, one of two things will happen:

  1. The pizza arrives at your door. The promise is fulfilled.
  2. The restaurant calls to say they're out of cheese. The promise is rejected.

Either way, the initial promise (the receipt) is settled, and you can then react to the outcome. Promises allow us to write asynchronous code that is much cleaner and easier to reason about, escaping the pyramid of doom.

Quiz Questions 1/5

In the context of JavaScript, what is synchronous programming?

Quiz Questions 2/5

Why is 'blocking the main thread' considered a major problem in JavaScript for web development?