Web Programming
Lecture 5

Asynchronous JavaScript: Why and How

Josue Obregon

Seoul National University of Science and Technology
Information Technology Management
Lecture slides index

March 28, 2026

Course structure

Roadmaps: Frontend, backend and fullstack

Agenda

  • What is asynchronous programming?
  • Why JavaScript needs it?
  • Callbacks and timers
  • Limitations of callbacks

Synchronous vs Asynchronous

Synchronous

  • Code runs step by step (one task at a time)
  • Each task must finish before the next starts
  • Can cause delays or “freezing” in the browser

Asynchronous

  • Tasks can start and finish independently
  • The program does not wait for slow operations
  • Keeps the application responsive

Asynchronous programming allows JavaScript to handle multiple tasks without blocking the user interface

Real example: loading images

Synchronous system

  • Images load one by one
  • Next image waits for the previous one
  • Slow user experience

Asynchronous system

  • Images load independently
  • Faster display
  • Better user experience

Why does JS need asynchronous programming?

  • JavaScript runs on a single thread
  • It can only execute one task at a time

Problem:

  • If one task is slow (e.g., loading data from a server), the entire application stops responding
  • Examples of this situation:
    • Clicking a button
    • Typing in an input
    • Animations
  • All can freeze while waiting for a slow task

The asynchronous mindset

  • To master asynchronous programming, start thinking about your code in a non-linear way.
    • Do not think in a strict sequence (step 1 → step 2 → step 3)
  • Instead of writing linear code, structure it around events and outcomes.
    • What happens when an event occurs?
    • What should run when a task finishes?
  • Examples of events:
    • User clicks a button
    • Data finishes loading
    • Timer completes

Event listeners are asynchronous

  • When we add an event listener, the function is not executed immediately
  • Instead, the function runs later, when the event happens
  • Recall the following code:
btn.addEventListener('click', () => {
    console.log("Button clicked!");
});
  • The code inside the anonymous function does NOT run right away
  • It waits until the user clicks the button
  • This pattern is called a callback function

Execution Order: What Happens First?

console.log("A");

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

console.log("C");
  • What is printed in the console?
    1. A → B → C
    2. A → C → B
    3. B → A → C
  • Answer: A → C → B
  • setTimeout runs later, even with 0 ms
  • JavaScript continues executing the next lines

Callback functions

  • A callback is a function passed as an argument to another function
  • The function is not executed immediately… it is executed later, when needed
  • Let’s see an example of a callback function
  • nextStep is passed as a function
  • It is called inside doSomething
  • It runs after the first task
  • “Call me when you are done”
function doSomething(callback) {
    console.log("Doing something...");
    callback();
}

function nextStep() {
    console.log("Callback executed");
}

doSomething(nextStep);

Callbacks with data

  • Callbacks can receive values as argument, this is how we get results from a task
  • The function computes a result (length)
  • The result is passed to the callback
  • The callback decides what to do next
function calculateNameLength(name, callback) {
    const length = name.length;
    callback(length);
}

calculateNameLength("John", (length) => {
    console.log("Length:", length);
});

Note

Callbacks pass results from one step to another

Timers and intervals

Name Description
setTimeout(callBack, delay) Runs callBack once after a delay in ms, returns timer id
setInterval(callBack, delay) Runs callback repeatedly every delay in ms, returns timer id
clearTimeout(timerID) Stop the given timer
clearInterval(timerID) Stop the given interval
  • Commonly used functions to delay the execution of a function
  • Both functions do not execute immediately, but they schedule a callback to run later
  • Both setTimeout and setInterval return an ID representing the timer.
    • You can stop that particular timer by passing the id to clearTimeout/clearInterval functions

setTimeout example

<button id="demo-btn">Click me!</button>
<p id="output-text">Before clicking the button...</p>
document.addEventListener('DOMContentLoaded', () => {
    document.getElementById("demo-btn").addEventListener("click", delayedMessage);
});

function delayedMessage() {
    document.getElementById("output-text").textContent = "Wait for it...";

    setTimeout(showMessage, 3000);
}

function showMessage() {
    document.getElementById("output-text").textContent = "Done!";
}

setInterval example

<p id="timer-text">Timer text</p>
<button id="start-btn">Start</button>
<button id="stop-btn">Stop</button>
let timerId = null;

document.getElementById("start-btn").addEventListener("click", startTimer);
document.getElementById("stop-btn").addEventListener("click", stopTimer);

function startTimer() {
    timerId = setInterval(() => {
        document.getElementById("timer-text").textContent += "⏱️ ";
    }, 1000);
}

function stopTimer() {
    clearInterval(timerId);
}

Error first callbacks

  • The most common way to handle errors in callbacks is to use the error first pattern.
    • First parameter → error
    • Second parameter → result
  • We check the error before using the result
function doSomething(callback) {
    const error = null; // or an error
    const result = "Success";

    callback(error, result);
}

doSomething((error, result) => {
    if (error) {
        console.log("Error occurred");
        return;
    }

    console.log(result);
});

Callback hell

  • Callbacks are not easy to read, and when there are a lot of nested callbacs, the code becomes very hard to understand.
  • This is called callback hell, and it is a common problem when using callbacks.
readFile("file.txt", (err, data) => {
    processData(data, (err, result) => {
        saveData(result, (err) => {
            sendToServer((err) => {
                console.log("Done");
            });
        });
    });
});

Problems

  • Deep nesting
  • Hard to follow logic
  • Difficult to debug

Why is callback hell a problem?

Task 1
  └── Task 2
        └── Task 3
              └── Task 4
                    └── Task 5
  • Code moves deeper to the right
    • Each step depends on the previous one
  • Hard to read and understand
    • Everything becomes nested inside callbacks
  • Hard to modify or debug

We need a better way to manage asynchronous code!

Next week

Promises, Async/Await, and Fetch

(Addendum) Using data attributes data-attribute

  • Allow us to store extra information on standard HTML elements
  • Any attribute on any element whose attribute name starts with data- is a data attribute
  • In JS we can get data attributes through the dataset object
    • You can access the property using the part of the attribute name after data- (see the example)

HTML

<button data-color="red">Red</button>

JavaScript

const button = document.querySelector("button");

button.dataset.color; // 'red'

Acknowledgements


Back to title slide Back to lecture slides index