Understanding Request Waterfalls: Impact on Web Performance

A big issue that often slows down apps is when requests wait in line, with each one starting only after the last one finishes. Check out the code below for an example:

async function fetchTodo1() {
  await fetch('https://jsonplaceholder.typicode.com/todos/1');
  console.log(`Todo 1 loaded`);
}

async function fetchTodo2() {
  await fetch('https://jsonplaceholder.typicode.com/todos/2');
  console.log(`Todo 2 loaded`);
}

async function fetchTodo3() {
  await fetch('https://jsonplaceholder.typicode.com/todos/3');
  console.log(`Todo 3 loaded`);
}

// Executing requests using an immediately-invoked function expression (IIFE)
(async function loadAllTodos() {
  let startTime = Date.now();

  await fetchTodo1();
  await fetchTodo2();
  await fetchTodo3();

  console.log(`All todos loaded in ${Date.now() - startTime}ms`);
})();

Here, we have three separate functions, each making a different request. We then call these functions one after another in a self-invoking async function. This means each todo will be fetched only after the previous one has finished loading.

This brings us to the key concept of a 'Request Waterfall,' where web browser requests for page resources are made one after another, sequentially.

The Impact of Sequential Requests

Request waterfalls could lead to various performance bottlenecks such as:

  • Longer Load Times: Each request is made one at a time. Even though they are asynchronous, the total time for your website to fully load increases.

  • Poor User Experience: Slow loading can frustrate users.

  • Increased Server Load: Sequential requests keep your server busy for longer periods, especially under heavy traffic.

A Smarter Approach: Parallel Data Fetching

A better approach to avoiding request waterfalls is running requests in parallel using Promise.all() which is used for handling multiple promises simultaneously. Below is an example of our refactored code above:

let startTime = Date.now();

Promise.all([
  fetch('https://jsonplaceholder.typicode.com/todos/1'),
  fetch('https://jsonplaceholder.typicode.com/todos/2'),
  fetch('https://jsonplaceholder.typicode.com/todos/3')
]).then(() => {
  console.log(`All todos loaded in ${Date.now() - startTime}ms`);
});

Here, we’re requesting the same three todos, but this time we send all the requests at once.

In small apps, loading things one by one might not look like a big deal initially, as it doesn't slow things down much. But as your app gets bigger and more complex, these little delays start to add up, making your app slower and less efficient.

Handling Dependencies and Performance Optimization

In cases where one request depends on another, like fetching user details before their posts, performance is key. Backend optimizations such as improving database queries or implementing caching can help fetch data faster, even when requests are dependent.

Hope you found this helpful! πŸ‘ Stay tuned for more updates. 🌟

Β