AJAX  

Chapter 11: Asynchronous JavaScript: Fetching Data and Timers

Up to this point, our code has been synchronous—it runs sequentially, one line after the other. However, tasks like fetching data from a server, setting a timer, or accessing a user's location can take time. If JavaScript waited for these tasks to finish, the browser would freeze. Asynchronous JavaScript is the solution, allowing code to run in the background without blocking the main program execution.

The JavaScript Event Loop

JavaScript is single-threaded, but the browser provides Web APIs (like setTimeout, fetch, DOM) that can handle time-consuming tasks outside the main thread. The Event Loop is the mechanism that ensures when these tasks complete, their associated callback functions are pushed back onto the main thread for execution without blocking the UI.

Timers: setTimeout and setInterval

These are the simplest forms of asynchronous operations.

1. setTimeout(): Delayed Execution

Runs a function once after a specified delay (in milliseconds).

JavaScript

console.log('Start');

setTimeout(() => {
    console.log('This runs after 3 seconds (asynchronously)');
}, 3000);

console.log('End (This runs immediately)');
// Output order: Start, End, ... 3 seconds later: This runs...

To cancel a timeout, save its ID and use clearTimeout():

JavaScript

const timerID = setTimeout(() => console.log('This will be canceled'), 5000);
clearTimeout(timerID); // The function never runs

2. setInterval(): Repetitive Execution

Runs a function repeatedly, starting after the specified interval.

JavaScript

let count = 0;
const intervalID = setInterval(() => {
    count++;
    console.log(`Count: ${count}`);
    if (count >= 5) {
        clearInterval(intervalID); // Stop the interval
    }
}, 1000);

Introduction to Promises

Before Promises, asynchronous code relied heavily on callbacks, often leading to messy, hard-to-read code (known as "Callback Hell"). Promises are a modern object-based solution for handling asynchronous results.

A Promise represents a value that is not necessarily known when the promise is created. It is a container for a future value.

A Promise is always in one of three states:

  1. Pending: Initial state, neither fulfilled nor rejected.

  2. Fulfilled (Resolved): The operation completed successfully, and the promise has a resulting value.

  3. Rejected: The operation failed, and the promise has a reason for the failure (an error object).

Consuming Promises

We consume the eventual result of a Promise using the .then() and .catch() methods.

  • .then(onFulfilled, onRejected): Registers callbacks to be called when the Promise is fulfilled or rejected.

  • .catch(onRejected): A shorthand for .then(null, onRejected), typically used for error handling at the end of a chain.

  • .finally(): Runs regardless of success or failure (good for cleanup).

JavaScript

// Imagine a function that returns a Promise
const myPromise = new Promise((resolve, reject) => {
    // Simulate a successful operation
    setTimeout(() => resolve('Data loaded successfully'), 2000);
});

myPromise
    .then(result => {
        console.log('Success:', result); // Output: Data loaded successfully
        return 'Processed: ' + result; // Pass result to the next .then()
    })
    .then(processedData => {
        console.log(processedData);
    })
    .catch(error => {
        console.error('An error occurred:', error);
    })
    .finally(() => {
        console.log('Promise finished.');
    });

Fetching Data with the fetch API

The fetch API is the standard way to make network requests in modern JavaScript, and it is Promise-based.

JavaScript

// Fetch data from an external API (Application Programming Interface)
fetch('https://api.example.com/data')
    .then(response => {
        // The first .then() checks if the request was successful and parses the response body (e.g., as JSON)
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return response.json(); // Returns a new Promise with the parsed JSON data
    })
    .then(data => {
        // The second .then() receives the final JavaScript object
        console.log('Received data:', data);
        // Display data in the DOM here
    })
    .catch(error => {
        // Catches any error during the fetch or the parsing
        console.error('There was a problem with the fetch operation:', error);
    });