Episode 1 of 5

What is Asynchronous JavaScript?

Understand what asynchronous code means, how JavaScript's single-threaded event loop works, and why async patterns are essential for modern web development.

What is Asynchronous JavaScript?

Welcome to the Asynchronous JavaScript tutorial! In this series you will learn how to write non-blocking code that can handle time-consuming tasks like network requests, file reading, and timers — without freezing your entire application.

Synchronous vs Asynchronous

By default, JavaScript executes code synchronously — one line at a time, top to bottom. Each line must finish before the next one starts.

console.log('First');
console.log('Second');
console.log('Third');

// Output:
// First
// Second
// Third

This is easy to understand, but it creates a problem. What if one line takes a long time to complete?

The Blocking Problem

console.log('Start');
// Imagine this takes 5 seconds...
const data = fetchDataFromServer();  // Blocks everything
console.log(data);
console.log('End');

// Nothing else can run for 5 seconds!
// The page freezes, buttons don't respond, animations stop.

If JavaScript had to wait for every network request, database query, or file read to finish before moving on, web pages would be completely unresponsive during those operations. Users would see frozen screens and unclickable buttons.

The Solution: Asynchronous Code

Asynchronous code lets JavaScript start a task and move on without waiting for it to finish. When the task completes, a callback function runs to handle the result.

console.log('Start');

setTimeout(() => {
    console.log('This runs after 2 seconds');
}, 2000);

console.log('End');

// Output:
// Start
// End
// This runs after 2 seconds

Notice that 'End' prints before the timeout message. JavaScript did not wait for the 2-second timer — it moved on to the next line immediately and came back to the callback when the timer finished.

JavaScript Is Single-Threaded

JavaScript runs on a single thread — it can only execute one piece of code at a time. So how does it handle asynchronous operations without additional threads? The answer is the event loop.

The Event Loop

The event loop is the mechanism that allows JavaScript to perform non-blocking operations despite being single-threaded. It works with several components:

ComponentRole
Call StackWhere currently executing code lives — functions are pushed on and popped off
Web APIsBrowser-provided APIs that handle async tasks (setTimeout, fetch, DOM events)
Callback QueueWhere completed async callbacks wait to be executed
Event LoopContinuously checks: "Is the call stack empty? If yes, take the next item from the queue"

How It All Works Together

console.log('Start');           // 1. Pushed to call stack, executes, popped off

setTimeout(() => {              // 2. setTimeout is a Web API
    console.log('Timer done');  //    Browser starts the timer in the background
}, 2000);                       //    setTimeout itself is popped off the stack

console.log('End');             // 3. Pushed to call stack, executes, popped off

// ... 2 seconds pass ...
// 4. Timer completes, callback moves to the callback queue
// 5. Event loop checks: call stack is empty!
// 6. Event loop moves callback from queue to call stack
// 7. console.log('Timer done') executes

Visual Timeline

CALL STACK          WEB APIs            CALLBACK QUEUE
─────────────       ──────────          ──────────────
log('Start')
                    setTimeout(2s)
log('End')
                    [timer counting]
[empty]             [timer done] ──→    callback()
callback() ←────────────────────────    [empty]
log('Timer done')

Common Asynchronous Operations

OperationWhy It's AsyncExample
Network requestsServer response time is unpredictablefetch(), XMLHttpRequest
TimersDelay before executionsetTimeout(), setInterval()
File operationsDisk I/O is slowfs.readFile() in Node.js
User eventsUser action timing is unknownclick, keypress, scroll
Database queriesQuery execution time variesMongoDB, MySQL queries
AnimationsRun over timerequestAnimationFrame()

Three Async Patterns in JavaScript

Over the years, JavaScript has developed three main patterns for handling asynchronous code. We will cover each one in detail in this series:

PatternIntroducedSyntax
CallbacksOriginal JavaScriptPass a function to run when done
PromisesES6 (2015).then() and .catch() chains
GeneratorsES6 (2015)function* with yield

Each pattern builds on top of the previous one to solve its pain points. Callbacks came first but led to "callback hell." Promises flattened the nesting. Generators introduced pausable functions that work elegantly with async flows.

A Quick Taste

// Callback style
getData(function(data) {
    getMore(data, function(moreData) {
        console.log(moreData);
    });
});

// Promise style
getData()
    .then(data => getMore(data))
    .then(moreData => console.log(moreData));

// Generator style (with a runner)
function* main() {
    const data = yield getData();
    const moreData = yield getMore(data);
    console.log(moreData);
}

Key Takeaways

  • Synchronous code runs line-by-line and blocks until each operation finishes
  • Asynchronous code starts a task and moves on — a callback handles the result later
  • JavaScript is single-threaded but uses the event loop to handle async operations without blocking
  • The event loop continuously checks if the call stack is empty and moves queued callbacks onto it
  • Network requests, timers, file I/O, and user events are all handled asynchronously
  • Three major async patterns: callbacks, Promises, and generators — each improving on the last