🚀 Efficient Concurrency Control in JavaScript: 3 Practical Solutions


:rocket: Efficient Concurrency Control in JavaScript: 3 Practical Solutions

Managing asynchronous operations efficiently is a critical part of modern JavaScript development. In scenarios where multiple API requests need to be coordinated, choosing the right concurrency strategy can impact performance and code clarity. Below, we explore three effective methods for handling concurrent requests in JavaScript, each suited to different dependencies and complexity levels.


:white_check_mark: Solution 1: Use Promise.all() to Run Tasks in Parallel

The simplest and most direct method is to use Promise.all() to initiate Request A and Request B simultaneously, and then proceed to Request C once both have completed.

async function fetchData() {
  try {
    // Execute A and B in parallel
    const [resultA, resultB] = await Promise.all([
      fetchRequestA(),
      fetchRequestB()
    ]);
    
    // Proceed with C after A and B complete
    const resultC = await fetchRequestC(resultA, resultB);
    
    return resultC;
  } catch (error) {
    console.error('Request failed:', error);
    throw error;
  }
}

:white_check_mark: Pros:

  • Clean and readable code
  • Perfect when Request C depends on A and B

:warning: Cons:

  • Inefficient if Request C does not depend on A or B — it waits unnecessarily

:white_check_mark: Solution 2: Trigger Requests Simultaneously and Handle Dependencies Later

If Request C is independent of A and B, you can start all three requests simultaneously, then wait only for A and B to finish before processing C’s result.

async function fetchData() {
  try {
    // Start all requests at once
    const promiseA = fetchRequestA();
    const promiseB = fetchRequestB();
    const promiseC = fetchRequestC();
    
    // Wait for A and B to complete
    const [resultA, resultB] = await Promise.all([promiseA, promiseB]);
    
    // Retrieve C’s result (may already be done)
    const resultC = await promiseC;
    
    return { resultA, resultB, resultC };
  } catch (error) {
    console.error('Request failed:', error);
    throw error;
  }
}

:white_check_mark: Pros:

  • Non-blocking: C starts immediately without waiting for A and B
  • Great when C is unrelated to A and B

:warning: Cons:

  • Slightly more complex code
  • You must ensure C truly doesn’t rely on A or B

:white_check_mark: Solution 3: Build a Custom Concurrency Controller

For advanced scenarios—such as limiting the number of concurrent requests—you can implement a custom request controller to manage concurrency more granularly.

class RequestController {
  constructor(maxConcurrency = 2) {
    this.runningCount = 0;
    this.maxConcurrency = maxConcurrency;
    this.queue = [];
  }

  async addRequest(requestFn) {
    if (this.runningCount >= this.maxConcurrency) {
      await new Promise(resolve => this.queue.push(resolve));
    }

    this.runningCount++;

    try {
      return await requestFn();
    } finally {
      this.runningCount--;
      if (this.queue.length > 0) {
        const next = this.queue.shift();
        next();
      }
    }
  }
}

// Usage
async function fetchData() {
  const controller = new RequestController();

  const promiseA = controller.addRequest(fetchRequestA);
  const promiseB = controller.addRequest(fetchRequestB);

  await Promise.all([promiseA, promiseB]);

  const resultC = await fetchRequestC();
  return resultC;
}

:white_check_mark: Pros:

  • Precise control over concurrency
  • Scalable and reusable as a utility class

:warning: Cons:

  • Requires more code and setup
  • Best suited for applications needing custom concurrency management

:pushpin: Choosing the Right Solution

Scenario Recommended Approach
Request C depends on A and B results :white_check_mark: Use Solution 1
Request C is independent of A and B :white_check_mark: Use Solution 2
Need to limit concurrent requests or reuse :white_check_mark: Use Solution 3

By selecting the appropriate strategy based on your application’s data flow and performance requirements, you can enhance both efficiency and maintainability in your asynchronous JavaScript code.