Frontend Development 8 min read

Controlling Concurrent Requests in JavaScript with Promise.all, Promise.race, and async/await

This article explains how to manage multiple asynchronous HTTP requests in modern web development by using Promise.all, Promise.race, async/await, manual counters, and third‑party libraries, providing complete code examples and best‑practice recommendations for limiting concurrency and improving application performance.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Controlling Concurrent Requests in JavaScript with Promise.all, Promise.race, and async/await

Opening

In modern web development, asynchronous requests are essential, but handling many requests simultaneously can cause conflicts and chaos; this article explores how to control concurrent requests using Promise methods.

JavaScript offers several ways to manage asynchronous concurrency, such as Promise.all() , Promise.race() , and async/await . Below is an example that uses Promise.all() to limit concurrency.

Promise.all()

const urls = ["url1", "url2", ... ,"url100"]; 
const maxConcurrentNum = 10; // maximum concurrent requests 
// Split array into chunks, each chunk size = maxConcurrentNum
function chunk(arr, chunk) {
  let result = [];
  for (let i = 0, len = arr.length; i < len; i += chunk) {
    result.push(arr.slice(i, i + chunk));
  }
  return result;
}

// Async request function
function fetchUrl(url) {
  return new Promise((resolve, reject) => {
    fetch(url)
      .then(res => resolve(res))
      .catch(err => reject(err));
  });
}

// Chunk the URLs
const chunkedUrls = chunk(urls, maxConcurrentNum);

(async function () {
  try {
    for (let urls of chunkedUrls) {
      const promises = urls.map(url => fetchUrl(url));
      const results = await Promise.all(promises);
      console.log('results:', results);
    }
  } catch (err) {
    console.error(err);
  }
})();

The code splits the URL list into equally sized sub‑arrays and launches at most maxConcurrentNum requests at a time; once a batch finishes, the next batch starts, effectively throttling concurrency.

Promise.race()

The following example shows how to use Promise.race() to obtain the fastest response among many requests.

const promiselist = [];
for (let i = 0; i < 100; i++) {
  const promise = fetch(`https://example.com/data${i}.json`);
  promiselist.push(promise);
}
Promise.race(promiselist)
  .then(response => {
    // handle the fastest response here
  })
  .catch(error => {
    console.error(error);
  });

async/await

Using async/await together with Promise.all() provides a clear way to fire many requests and wait for all of them.

async function getData() {
  const promiselist = [];
  for (let i = 0; i < 100; i++) {
    const promise = fetch(`https://example.com/data${i}.json`);
    promiselist.push(promise);
  }
  const responses = await Promise.all(promiselist);
  for (const response of responses) {
    // handle each response here 
  }
}

getData().catch(error => {
  console.error(error);
});

If only the quickest result is needed, wrap Promise.race() inside an async function, similar to the Promise.all() approach.

Beyond these built‑in methods, other techniques exist for concurrency control:

Manual counter : Use a variable to track active requests and pause new ones until a slot frees up. Example: function getData() { const limit = 5; // maximum concurrent requests const dataUrls = [ 'https://example.com/data1.json', 'https://example.com/data2.json', 'https://example.com/data3.json', 'https://example.com/data4.json', 'https://example.com/data5.json', 'https://example.com/data6.json' ]; let counter = 0; const getDataPromise = dataUrl => { return new Promise((resolve, reject) => { fetch(dataUrl) .then(response => { counter--; resolve(response); }) .catch(error => { counter--; reject(error); }); }); }; const getDataPromises = dataUrls.map(dataUrl => { if (counter < limit) { counter++; return getDataPromise(dataUrl); } else { return new Promise(resolve => { const interval = setInterval(() => { if (counter < limit) { counter++; clearInterval(interval); resolve(getDataPromise(dataUrl)); } }, 100); }); } }); Promise.all(getDataPromises) .then(responses => { for (const response of responses) { // handle each response here } }) .catch(error => { console.error(error); }); } getData(); This code manually tracks the number of active requests and uses setInterval to wait for an available slot.

Third‑party libraries : Libraries such as async.js and p-limit provide ready‑made concurrency utilities. p-limit is a lightweight tool specifically for limiting Promise concurrency; see its documentation for usage examples.

Summary

By mastering Promise techniques—including Promise.all , Promise.race , async/await , manual counters, and dedicated libraries—you can efficiently control concurrent requests, making your web applications smoother and more responsive.

JavaScriptconcurrencyWeb DevelopmentAsync/AwaitPromise
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.