Week 10: Asynchronous JavaScript

INFO 253A: Front-end Web Architecture

Kay Ashaolu

JavaScript Chapter 10: Asynchronous JavaScript

Synchronous vs Asynchronous JavaScript

  • Synchronous JavaScript:
    • Executes code sequentially.
    • Each operation waits for the previous one to complete.
  • Asynchronous JavaScript:
    • Allows multiple operations to occur simultaneously.
    • Non-blocking: Does not wait for an operation to finish before moving on.
  • JavaScript is single-threaded but can manage asynchronous tasks via the event loop.

The Call Stack and Execution Context

  • Call Stack:
    • Tracks function calls and manages execution order.
    • Functions are pushed and popped off the stack.
  • Execution Context:
    • Contains variables, objects, and the value of this.
  • Blocking Example:
 function longTask() {
   // Simulate a long task
   for (let i = 0; i < 1e9; i++) {}
 }
 console.log('Start');
 longTask();
 console.log('End');

Web APIs and Asynchronous Capabilities

  • Web APIs provided by the browser enable asynchronous operations.
  • Include:
    • DOM Events
    • Timers (setTimeout, setInterval)
    • AJAX (XMLHttpRequest, Fetch API)
    • Promises
  • Extend JavaScript's capabilities beyond its core synchronous nature.

The Event Loop and Task Queue

  • Event Loop:
    • Continuously checks the Call Stack and Task Queue.
    • Moves tasks from the Task Queue to the Call Stack when it's empty.
  • Task Queue:
    • Holds callback functions waiting to be executed.
  • Microtask Queue:
    • Holds promise callbacks.
    • Has higher priority than the Task Queue.

Understanding Callbacks and Callback Hell

  • Callbacks:
    • Functions passed as arguments to be executed later.
    • Used for handling asynchronous operations.
  • Callback Hell:
    • Pyramid of doom caused by multiple nested callbacks.
    • Makes code hard to read and maintain.
  • Example of Callback Hell:
 doFirstTask((result1) => {
   doSecondTask(result1, (result2) => {
     doThirdTask(result2, (result3) => {
       // Continue nesting...
     });
   });
 });

Introducing Promises

  • Promises simplify asynchronous code.
  • States:
    • Pending: Initial state.
    • Fulfilled: Operation completed successfully.
    • Rejected: Operation failed.
  • Benefits:
    • Avoids callback hell.
    • Enables cleaner, more readable code.

Creating and Using Promises

  • Creating a Promise:
 const promise = new Promise((resolve, reject) => {
   // Asynchronous operation
   if (success) {
     resolve(result);
   } else {
     reject(error);
   }
 });
  • Consuming a Promise:
 promise
   .then((result) => {
     // Handle success
   })
   .catch((error) => {
     // Handle error
   });

Promise Chaining

  • Sequentially execute asynchronous tasks.
  • Example:
 getUser()
   .then((user) => getProfile(user.id))
   .then((profile) => getPosts(profile.id))
   .then((posts) => console.log(posts))
   .catch((error) => console.error(error));

HTTP Requests in Web Architecture

  • HTTP Requests enable communication between client and server.
  • Common HTTP Methods:
    • GET: Retrieve data.
    • POST: Send data.
    • PUT/PATCH: Update data.
    • DELETE: Remove data.
  • Asynchronous requests prevent UI blocking during data fetch.

Fetch API and XMLHttpRequest (XHR)

  • Fetch API:
    • Modern interface for making HTTP requests.
    • Returns a Promise.
    • Simplifies request code.
 fetch('https://api.example.com/data')
   .then((response) => response.json())
   .then((data) => console.log(data));
  • XMLHttpRequest (XHR):
    • Older method.
    • More verbose and complex.
    • Still important for understanding legacy code.

Handling Multiple Promises with Promise.all()

  • Promise.all() waits for multiple promises to resolve.
  • Useful for parallel asynchronous operations.
  • Example:
 Promise.all([fetchData1(), fetchData2(), fetchData3()])
   .then((results) => {
     // results is an array of resolved values
     console.log(results);
   })
   .catch((error) => console.error(error));

Async/Await Syntax

  • Async/Await simplifies working with promises.
  • Makes asynchronous code look synchronous.
  • Example:
 async function loadData() {
   try {
     const response = await fetch('https://api.example.com/data');
     const data = await response.json();
     console.log(data);
   } catch (error) {
     console.error(error);
   }
 }

Key Takeaways

  • Asynchronous JavaScript is essential for modern web applications.
  • Callbacks, Promises, and Async/Await help manage asynchronous operations.
  • Understanding these concepts improves code readability and maintainability.
  • Event Loop and Web APIs enable JavaScript to handle concurrent tasks effectively.

JavaScript Chapter 11: Fetch API and Async/Await

Asynchronous JavaScript in Web Architecture

  • Understanding modern asynchronous operations in JavaScript
  • Simplifying HTTP requests and responses
  • Enhancing front-end web architecture with Fetch API and Async/Await

Introduction to the Fetch API

  • What is the Fetch API?
    • A modern, promise-based interface for making HTTP requests
  • Key Features:
    • Simplifies HTTP requests in JavaScript
    • Provides a clean interface for fetching resources (e.g., JSON data, images)
    • Returns Promises, enabling easier handling of asynchronous operations
  • Replaces older XMLHttpRequest (XHR) methods

Basic Usage of Fetch API

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => {
    // Handle the data
    console.log(data);
  })
  .catch(error => {
    // Handle errors
    console.error('Error:', error);
  });
  • Step-by-Step Explanation:
    • fetch() initiates a network request to the provided URL
    • The first .then() processes the response, parsing it as JSON
    • The second .then() handles the parsed data
    • .catch() handles any errors that occur during the fetch or processing

Fetching JSON Data

  • Fetch API defaults to GET requests
  • Handling the response:
fetch('movies.json')
  .then(response => response.json())
  .then(data => {
    // Use the data
    console.log(data);
  });
  • Use response.json() to parse JSON responses

Fetch API Options: Method, Headers, Body

  • Customizing requests with options object:
fetch('https://api.example.com/data', {
  method: 'POST', // HTTP method
  headers: {
    'Content-Type': 'application/json', // Specifies the media type
    'Authorization': 'Bearer abc123' // Example of an auth token
  },
  body: JSON.stringify({ title: 'New Post', body: 'This is the content.' }) // Data to send
})
  .then(response => response.json())
  .then(data => {
    // Handle the response
    console.log(data);
  });

Fetch API Options: Method, Headers, Body

  • Explanation of options:
    • method: Specifies the HTTP method (GET, POST, PUT, DELETE)
    • headers: An object containing any custom headers you want to add
    • body: The data to send with the request (for POST, PUT)

Handling Errors with Fetch API

  • Understanding Fetch API error handling:
    • Fetch promises only reject on network failure
    • HTTP errors (e.g., 404, 500) do not automatically reject the promise
  • Manual error handling:
fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      // Response status is not in the range 200-299
      throw new Error('Network response was not ok ' + response.statusText);
    }
    return response.json();
  })
  .then(data => {
    // Handle data
    console.log(data);
  })
  .catch(error => {
    // Handle errors
    console.error('Fetch error:', error);
  });

Handling Errors with Fetch API

  • Key Points:
    • Check response.ok to determine if the request was successful
    • Throw an error to reject the promise if the response is not ok
    • Use .catch() to handle any errors that occur

Introduction to Async/Await

  • What is Async/Await?
    • Introduced in ES2017 (ES8)
    • Allows writing asynchronous code in a synchronous style
  • Benefits:
    • Simplifies complex promise chains
    • Improves code readability and maintainability
  • Basic Syntax:
async function fetchData() {
  // Function declared with 'async' keyword
  const response = await fetch('https://api.example.com/data');
  // 'await' pauses the function until the promise resolves
  const data = await response.json();
  // Use the data
  console.log(data);
}

fetchData(); // Call the async function

Introduction to Async/Await

  • Key Points:
    • Functions containing await must be declared with async
    • await can only be used inside async functions
    • The await keyword makes JavaScript wait until the promise settles

Error Handling with Async/Await and Try...Catch

  • Handling Errors in Async Functions:
    • Use try...catch blocks to handle errors in async functions
  • Example:
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      // Throw an error if response status is not OK
      throw new Error('HTTP error! status: ' + response.status);
    }
    const data = await response.json();
    // Use the data
    console.log(data);
  } catch (error) {
    // Handle any errors
    console.error('Error:', error);
  }
}

Error Handling with Async/Await and Try...Catch

  • Key Points:
    • try block contains code that may throw an error
    • catch block handles any errors that occur in the try block
    • Ensures errors are properly caught and handled

Mini-Project: Random User Generator

  • Goal:
    • Create an application that fetches and displays random user data
  • API Used:
  • Implementation Steps:
    1. Fetch user data using Fetch API and Async/Await
    2. Parse and extract necessary information (e.g., name, email, picture)
    3. Dynamically update the DOM to display user information
  • Code Sample:
  • Demonstrates:
    • Practical use of Fetch API and Async/Await
    • Error handling in asynchronous functions
    • Dynamic DOM manipulation

Mini-Project: Random User Generator

async function getRandomUser() {
  try {
    const response = await fetch('https://randomuser.me/api/');
    const data = await response.json();
    // Extract user data
    const user = data.results[0];
    displayUser(user);
  } catch (error) {
    console.error('Error fetching user:', error);
  }
}

function displayUser(user) {
  // Update the DOM elements with user information
  document.getElementById('user-name').textContent = `${user.name.first} ${user.name.last}`;
  document.getElementById('user-email').textContent = user.email;
  document.getElementById('user-picture').src = user.picture.large;
}
  • Demonstrates:
    • Practical use of Fetch API and Async/Await
    • Error handling in asynchronous functions
    • Dynamic DOM manipulation

Mini-Project: Typicode Todos - Part 1

  • Objective:
    • Build a simple todo application interacting with a mock API
  • API Used:
  • Features Implemented:
    • Fetching todos from the API and displaying them
    • Limiting the number of todos displayed
    • Adding new todos via POST requests
  • Code Sample - Fetching Todos:
  • Key Concepts:
    • Using query parameters (?_limit=5) to limit results
    • Making POST requests to add data
    • Handling JSON responses

Mini-Project: Typicode Todos - Part 1

  • Code Sample - Fetching Todos:
async function getTodos() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=5');
    const todos = await response.json();
    // Display todos in the DOM
    todos.forEach(todo => {
      // Create DOM elements and append to the list
    });
  } catch (error) {
    console.error('Error fetching todos:', error);
  }
}
  • Key Concepts:
    • Using query parameters (?_limit=5) to limit results
    • Making POST requests to add data
    • Handling JSON responses

Mini-Project: Typicode Todos - Part 1

  • Code Sample - Adding a Todo:
async function addTodo() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        title: 'New Todo',
        completed: false
      })
    });
    const newTodo = await response.json();
    // Update the DOM with the new todo
  } catch (error) {
    console.error('Error adding todo:', error);
  }
}
  • Key Concepts:
    • Using query parameters (?_limit=5) to limit results
    • Making POST requests to add data
    • Handling JSON responses

Mini-Project: Typicode Todos - Part 2

  • Extended Features:
    • Updating existing todos (e.g., marking as completed)
    • Deleting todos
  • Updating a Todo with PUT:
async function updateTodo(id, completed) {
  try {
    const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ completed })
    });
    const updatedTodo = await response.json();
    // Reflect changes in the DOM
  } catch (error) {
    console.error('Error updating todo:', error);
  }
}

Mini-Project: Typicode Todos - Part 2

  • Deleting a Todo with DELETE:
async function deleteTodo(id) {
  try {
    await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`, {
      method: 'DELETE'
    });
    // Remove todo from the DOM
  } catch (error) {
    console.error('Error deleting todo:', error);
  }
}
  • Key Concepts:
    • Using dynamic URLs with template literals
    • Performing PUT and DELETE requests
    • Understanding HTTP methods for RESTful APIs

Fetch API in Web Architecture

  • Role in Modern Web Applications:
    • Central to client-server communication
    • Facilitates fetching and sending data asynchronously
  • Advantages over Older Methods:
    • Cleaner syntax than XMLHttpRequest (XHR)
    • Returns Promises, enabling better error handling and chaining
  • Integration with Other Technologies:
    • Works seamlessly with modern JavaScript frameworks (e.g., React, Vue)
    • Can be used with service workers for caching and offline support

Best Practices with Fetch API and Async/Await

  • Error Handling:
    • Always use try...catch blocks in async functions
    • Check response.ok and handle HTTP errors
  • Code Organization:
    • Keep fetch calls and DOM manipulation separate
    • Use helper functions for repeated tasks (e.g., error handling)

Conclusion and Further Resources

  • Key Takeaways:
    • Fetch API and Async/Await simplify asynchronous programming
    • Essential tools for modern front-end development
    • Enable cleaner, more maintainable code

Week 10 - Async JavaScript / Fetch API

By kayashaolu

Week 10 - Async JavaScript / Fetch API

Course Website: https://www.ischool.berkeley.edu/courses/info/253a

  • 26